-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(sumologicextension): add implementation of Sumo Logic Extension (#…
…31031) **Description:** Add functionality based on Sumo Logic Repo code **Link to tracking Issue:** #29601 **Testing:** * Unit tests * manual tests * in use by customers for long time already **Documentation:** * code comments * README.md --------- Signed-off-by: Dominik Rosiek <[email protected]> Co-authored-by: Andrzej Stencel <[email protected]> Co-authored-by: Mikołaj Świątek <[email protected]>
- Loading branch information
1 parent
cea1de2
commit c7fbf38
Showing
19 changed files
with
3,324 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Use this changelog template to create an entry for release notes. | ||
|
||
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' | ||
change_type: new_component | ||
|
||
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) | ||
component: sumologicextension | ||
|
||
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). | ||
note: add implementation of Sumo Logic Extension | ||
|
||
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. | ||
issues: [29601] | ||
|
||
# (Optional) One or more lines of additional information to render under the primary note. | ||
# These lines will be padded with 2 spaces and then inserted directly into the document. | ||
# Use pipe (|) for multiline entries. | ||
subtext: | ||
|
||
# If your change doesn't affect end users or the exported elements of any package, | ||
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. | ||
# Optional: The change log or logs in which this entry should be included. | ||
# e.g. '[user]' or '[user, api]' | ||
# Include 'user' if the change is relevant to end users. | ||
# Include 'api' if there is a change to a library API. | ||
# Default: '[user]' | ||
change_logs: [user] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package api // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/sumologicextension/api" | ||
|
||
type ErrorResponsePayload struct { | ||
ID string `json:"id"` | ||
Errors []Error `json:"errors"` | ||
} | ||
|
||
type Error struct { | ||
Code string `json:"code"` | ||
Message string `json:"message"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package api // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/sumologicextension/api" | ||
|
||
type OpenMetadataHostDetails struct { | ||
Name string `json:"name"` | ||
OsName string `json:"osName"` | ||
OsVersion string `json:"osVersion"` | ||
Environment string `json:"environment"` | ||
} | ||
|
||
type OpenMetadataCollectorDetails struct { | ||
RunningVersion string `json:"runningVersion"` | ||
} | ||
|
||
type OpenMetadataNetworkDetails struct { | ||
HostIPAddress string `json:"hostIpAddress"` | ||
} | ||
|
||
type OpenMetadataRequestPayload struct { | ||
HostDetails OpenMetadataHostDetails `json:"hostDetails"` | ||
CollectorDetails OpenMetadataCollectorDetails `json:"collectorDetails"` | ||
NetworkDetails OpenMetadataNetworkDetails `json:"networkDetails"` | ||
TagDetails map[string]any `json:"tagDetails"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package api // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/sumologicextension/api" | ||
|
||
type OpenRegisterRequestPayload struct { | ||
CollectorName string `json:"collectorName"` | ||
Ephemeral bool `json:"ephemeral,omitempty"` | ||
Description string `json:"description,omitempty"` | ||
Hostname string `json:"hostname,omitempty"` | ||
Category string `json:"category,omitempty"` | ||
TimeZone string `json:"timeZone,omitempty"` | ||
Clobber bool `json:"clobber,omitempty"` | ||
Fields map[string]any `json:"fields,omitempty"` | ||
} | ||
|
||
type OpenRegisterResponsePayload struct { | ||
CollectorCredentialID string `json:"collectorCredentialID"` | ||
CollectorCredentialKey string `json:"collectorCredentialKey"` | ||
CollectorID string `json:"collectorId"` | ||
CollectorName string `json:"collectorName"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
257 changes: 257 additions & 0 deletions
257
extension/sumologicextension/credentials/credentialsstore_localfs.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,257 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package credentials // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/sumologicextension/credentials" | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"os" | ||
"path" | ||
|
||
"go.uber.org/zap" | ||
) | ||
|
||
const ( | ||
DefaultCollectorDataDirectory = ".sumologic-otel-collector/" | ||
) | ||
|
||
func GetDefaultCollectorCredentialsDirectory() (string, error) { | ||
home, err := os.UserHomeDir() | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return path.Join(home, DefaultCollectorDataDirectory), nil | ||
} | ||
|
||
// LocalFsStore implements Store interface and can be used to store and retrieve | ||
// collector credentials from local file system. | ||
// | ||
// Files are stored locally in collectorCredentialsDirectory. | ||
type LocalFsStore struct { | ||
collectorCredentialsDirectory string | ||
logger *zap.Logger | ||
} | ||
|
||
type LocalFsStoreOpt func(*LocalFsStore) | ||
|
||
func WithLogger(l *zap.Logger) LocalFsStoreOpt { | ||
return func(s *LocalFsStore) { | ||
s.logger = l | ||
} | ||
} | ||
|
||
func WithCredentialsDirectory(dir string) LocalFsStoreOpt { | ||
return func(s *LocalFsStore) { | ||
s.collectorCredentialsDirectory = dir | ||
} | ||
} | ||
|
||
func NewLocalFsStore(opts ...LocalFsStoreOpt) (Store, error) { | ||
defaultDir, err := GetDefaultCollectorCredentialsDirectory() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
logger, err := zap.NewDevelopment() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
store := LocalFsStore{ | ||
collectorCredentialsDirectory: defaultDir, | ||
logger: logger, | ||
} | ||
for _, opt := range opts { | ||
opt(&store) | ||
} | ||
|
||
return store, err | ||
} | ||
|
||
// Check checks if collector credentials can be found under a name being a hash | ||
// of provided key inside collectorCredentialsDirectory. | ||
func (cr LocalFsStore) Check(key string) bool { | ||
f := func(_ Hasher, key string) bool { | ||
filenameHash, err := HashKeyToFilename(key) | ||
if err != nil { | ||
return false | ||
} | ||
path := path.Join(cr.collectorCredentialsDirectory, filenameHash) | ||
if _, err := os.Stat(path); err != nil { | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
return f(_getHasher(), key) | ||
} | ||
|
||
// Get retrieves collector credentials stored in local file system and then | ||
// decrypts it using a hash of provided key. | ||
func (cr LocalFsStore) Get(key string) (CollectorCredentials, error) { | ||
f := func(_ Hasher, key string) (CollectorCredentials, error) { | ||
filenameHash, err := HashKeyToFilename(key) | ||
if err != nil { | ||
return CollectorCredentials{}, err | ||
} | ||
|
||
path := path.Join(cr.collectorCredentialsDirectory, filenameHash) | ||
creds, err := os.Open(path) | ||
if err != nil { | ||
return CollectorCredentials{}, err | ||
} | ||
defer creds.Close() | ||
|
||
encryptedCreds, err := io.ReadAll(creds) | ||
if err != nil { | ||
return CollectorCredentials{}, err | ||
} | ||
|
||
encKey, err := HashKeyToEncryptionKey(key) | ||
if err != nil { | ||
return CollectorCredentials{}, err | ||
} | ||
|
||
collectorCreds, err := decrypt(encryptedCreds, encKey) | ||
if err != nil { | ||
return CollectorCredentials{}, err | ||
} | ||
|
||
var credentialsInfo CollectorCredentials | ||
if err = json.Unmarshal(collectorCreds, &credentialsInfo); err != nil { | ||
return CollectorCredentials{}, err | ||
} | ||
|
||
cr.logger.Info("Collector registration credentials retrieved from local fs", | ||
zap.String("path", path), | ||
) | ||
|
||
return credentialsInfo, nil | ||
} | ||
|
||
creds, err := f(_getHasher(), key) | ||
|
||
if err != nil { | ||
return CollectorCredentials{}, err | ||
} | ||
|
||
return creds, nil | ||
} | ||
|
||
// Store stores collector credentials in a file in directory as specified | ||
// in CollectorCredentialsDirectory. | ||
// The credentials are encrypted using the provided key. | ||
func (cr LocalFsStore) Store(key string, creds CollectorCredentials) error { | ||
if err := ensureDir(cr.collectorCredentialsDirectory); err != nil { | ||
return err | ||
} | ||
|
||
f := func(_ Hasher, key string, creds CollectorCredentials) error { | ||
filenameHash, err := HashKeyToFilename(key) | ||
if err != nil { | ||
return err | ||
} | ||
path := path.Join(cr.collectorCredentialsDirectory, filenameHash) | ||
collectorCreds, err := json.Marshal(creds) | ||
if err != nil { | ||
return fmt.Errorf("failed marshaling collector credentials: %w", err) | ||
} | ||
|
||
encKey, err := HashKeyToEncryptionKey(key) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
encryptedCreds, err := encrypt(collectorCreds, encKey) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err = os.WriteFile(path, encryptedCreds, 0600); err != nil { | ||
return fmt.Errorf("failed to save credentials file '%s': %w", | ||
path, err, | ||
) | ||
} | ||
|
||
cr.logger.Info("Collector registration credentials stored locally", | ||
zap.String("path", path), | ||
) | ||
|
||
return nil | ||
} | ||
|
||
err := f(_getHasher(), key, creds) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (cr LocalFsStore) Delete(key string) error { | ||
f := func(hasher Hasher, key string) error { | ||
filenameHash, err := HashKeyToFilenameWith(hasher, key) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
path := path.Join(cr.collectorCredentialsDirectory, filenameHash) | ||
|
||
if _, err := os.Stat(path); err != nil { | ||
return nil | ||
} | ||
if err := os.Remove(path); err != nil { | ||
return fmt.Errorf("failed to remove credentials file '%s': %w", | ||
path, err, | ||
) | ||
} | ||
|
||
cr.logger.Debug("Collector registration credentials removed", | ||
zap.String("path", path), | ||
) | ||
|
||
return nil | ||
} | ||
|
||
err := f(_getHasher(), key) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Validate checks if the store is operating correctly | ||
// This mostly means file permissions and the like | ||
func (cr LocalFsStore) Validate() error { | ||
if err := ensureDir(cr.collectorCredentialsDirectory); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// ensureDir checks if the specified directory exists and has the right permissions | ||
// if it doesn't then it tries to create it. | ||
func ensureDir(path string) error { | ||
fi, err := os.Stat(path) | ||
if err != nil { | ||
if err := os.Mkdir(path, 0700); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
// If the directory doesn't have the execution bit then | ||
// set it so that we can 'exec' into it. | ||
if fi.Mode().Perm() != 0700 { | ||
if err := os.Chmod(path, 0700); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.