forked from Keeper-Security/secrets-manager-go
-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Keep-142 AWS GO SDK Develop #3
Open
ayusha-metron
wants to merge
13
commits into
KEEP-142-aws-go-review
Choose a base branch
from
KEEP-142-aws-go-develop
base: KEEP-142-aws-go-review
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
4b4b961
Initial commit
ayusha-metron 8369466
Update go.cum
ayusha-metron e8c7666
Add encrypt function
ayusha-metron 20cefa1
Add readme file
ayusha-metron 2267018
Added lines and update deleteall() function
ayusha-metron 6e898c5
Remove commit line
ayusha-metron 9123d07
Update README.md file
ayusha-metron a326418
Add change key function
ayusha-metron 3a2a534
Update logger.go
ayusha-metron b542fc5
Added support for various keys
ayusha-metron 5a23dc8
Modify symmetric encryption and decryption code
ayusha-metron 873fce9
Added one line comments
ayusha-metron 60b8b50
Update logo
ayusha-metron File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 |
---|---|---|
@@ -1,2 +1,2 @@ | ||
github.com/keeper-security/secrets-manager-go/core v1.6.3 h1:XEHZ8fQ2DFBISK80jWdHmzT56PFqEkXSkakqZxTD8zI= | ||
github.com/keeper-security/secrets-manager-go/core v1.6.3/go.mod h1:dtlaeeds9+SZsbDAZnQRsDSqEAK9a62SYtqhNql+VgQ= | ||
github.com/keeper-security/secrets-manager-go/core v1.6.4 h1:ly2XvAgDxHoHVvFXOIYlxzxBF0yoQir1KfNHUNG4eRA= | ||
github.com/keeper-security/secrets-manager-go/core v1.6.4/go.mod h1:dtlaeeds9+SZsbDAZnQRsDSqEAK9a62SYtqhNql+VgQ= |
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,319 @@ | ||
// -*- coding: utf-8 -*- | ||
|
||
// _ __ | ||
// | |/ /___ ___ _ __ ___ _ _ (R) | ||
// | ' </ -_) -_) '_ \/ -_) '_| | ||
// |_|\_\___\___| .__/\___|_| | ||
// |_| | ||
// | ||
// Keeper Secrets Manager | ||
// Copyright 2025 Keeper Security Inc. | ||
// Contact: [email protected] | ||
|
||
package awskv | ||
|
||
import ( | ||
"awskv/aws/logger" | ||
"context" | ||
"crypto/md5" | ||
"encoding/hex" | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/aws/aws-sdk-go-v2/aws" | ||
"github.com/aws/aws-sdk-go-v2/config" | ||
"github.com/aws/aws-sdk-go-v2/credentials" | ||
"github.com/aws/aws-sdk-go-v2/service/kms" | ||
"github.com/aws/aws-sdk-go-v2/service/kms/types" | ||
"github.com/keeper-security/secrets-manager-go/core" | ||
) | ||
|
||
type AWSConfig struct { | ||
ClientID string | ||
ClientSecret string | ||
Region string | ||
} | ||
|
||
type AWSKeyVaultStorage struct { | ||
configFileLocation string | ||
config map[core.ConfigKey]interface{} | ||
lastSavedConfigHash string | ||
kmsClient *kms.Client | ||
keyARN string | ||
awsConfig *AWSConfig | ||
} | ||
|
||
// Creates a new instance of AWSKeyVaultStorage. | ||
func NewAWSKeyValueStorage(configFileLocation string, KeyARN string, awsSessionConfig *AWSConfig) *AWSKeyVaultStorage { | ||
if configFileLocation == "" { | ||
if envConfigFileLocation, ok := os.LookupEnv("KSM_CONFIG_FILE"); ok { | ||
configFileLocation = envConfigFileLocation | ||
} else { | ||
configFileLocation = core.DEFAULT_CONFIG_PATH | ||
} | ||
} | ||
|
||
cfg, err := getConfig(awsSessionConfig) | ||
if err != nil { | ||
return nil | ||
} | ||
|
||
if KeyARN == "" { | ||
logger.Errorf("Failed to create client secret credential: %v", err) | ||
return nil | ||
} | ||
|
||
// Generate a new AWS KMS client | ||
client := kms.NewFromConfig(*cfg) | ||
awsDetails := &AWSKeyVaultStorage{ | ||
configFileLocation: configFileLocation, | ||
config: make(map[core.ConfigKey]interface{}), | ||
lastSavedConfigHash: "", | ||
kmsClient: client, | ||
keyARN: KeyARN, | ||
awsConfig: awsSessionConfig, | ||
} | ||
|
||
keyData, err := awsDetails.getKeyDetails() | ||
// If key is not type of encrypt/decrypt, client operations will fail. | ||
if err != nil && keyData.KeyMetadata.KeyUsage != types.KeyUsageTypeEncryptDecrypt { | ||
logger.Errorf("Failed to create client secret credential: %v", err) | ||
return nil | ||
} | ||
|
||
err = awsDetails.loadConfig() | ||
if err != nil { | ||
return nil | ||
} | ||
|
||
return awsDetails | ||
} | ||
|
||
// Loads the decrypted configuration from the config file if encrypted config is present, else encrypts the config. | ||
func (a *AWSKeyVaultStorage) loadConfig() error { | ||
var config map[core.ConfigKey]interface{} | ||
var jsonError error | ||
var decryptionError bool | ||
var decryptData []byte | ||
|
||
if err := a.createConfigFileIfMissing(); err != nil { | ||
return err | ||
} | ||
|
||
contents, err := os.ReadFile(a.configFileLocation) | ||
if err != nil { | ||
logger.Errorf("Failed to load config file %s: %s", a.configFileLocation, err.Error()) | ||
return fmt.Errorf("failed to load config file %s", a.configFileLocation) | ||
} | ||
|
||
if len(contents) == 0 { | ||
logger.Errorf("Empty config file %s", a.configFileLocation) | ||
contents = []byte("{}") | ||
} | ||
|
||
if err := json.Unmarshal(contents, &config); err == nil { | ||
a.config = config | ||
if err := a.saveConfig(config); err != nil { | ||
return err | ||
} | ||
|
||
configJson, err := json.Marshal(config) | ||
if err != nil { | ||
return fmt.Errorf("failed to marshal config: %w", err) | ||
} | ||
|
||
a.lastSavedConfigHash = a.createHash(configJson) | ||
} else { | ||
jsonError = err | ||
} | ||
|
||
if jsonError != nil { | ||
keydata, err := a.getKeyDetails() | ||
if err != nil { | ||
} | ||
|
||
if keydata.KeyMetadata.KeySpec == types.KeySpecSymmetricDefault { | ||
decryptData, err = decryptSymmetric(a.kmsClient, a.keyARN, contents) | ||
if err != nil { | ||
decryptionError = true | ||
logger.Errorf("Failed to decrypt config file: %s", err.Error()) | ||
return fmt.Errorf("failed to decrypt config file %s", a.configFileLocation) | ||
} | ||
} else { | ||
decryptData, err = decryptAsymmetric(a.kmsClient, a.keyARN, contents) | ||
if err != nil { | ||
decryptionError = true | ||
logger.Errorf("Failed to decrypt config file: %s", err.Error()) | ||
return fmt.Errorf("failed to decrypt config file %s", a.configFileLocation) | ||
} | ||
} | ||
|
||
if err := json.Unmarshal(decryptData, &config); err != nil { | ||
decryptionError = true | ||
logger.Errorf("Failed to parse decrypted config file: %s", err.Error()) | ||
return fmt.Errorf("failed to parse decrypted config file %s", a.configFileLocation) | ||
} | ||
|
||
a.config = config | ||
a.lastSavedConfigHash = a.createHash(decryptData) | ||
} | ||
|
||
if jsonError != nil && decryptionError { | ||
logger.Errorf("Config file is not a valid JSON file: %s", jsonError.Error()) | ||
return fmt.Errorf("%s may contain JSON format problems", a.configFileLocation) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Saves the encrypted updated configuration to the config file and updates the hash of the config. | ||
func (a *AWSKeyVaultStorage) saveConfig(updatedConfig map[core.ConfigKey]interface{}) error { | ||
configJson, err := json.Marshal(a.config) | ||
if err != nil { | ||
return fmt.Errorf("failed to marshal config: %w", err) | ||
} | ||
|
||
configHash := a.createHash(configJson) | ||
if len(updatedConfig) > 0 { | ||
updatedConfigJson, err := json.Marshal(updatedConfig) | ||
if err != nil { | ||
return fmt.Errorf("failed to marshal updated config: %w", err) | ||
} | ||
|
||
updatedConfigHash := a.createHash(updatedConfigJson) | ||
if updatedConfigHash != configHash { | ||
configHash = updatedConfigHash | ||
a.config = make(map[core.ConfigKey]interface{}) | ||
for k, v := range updatedConfig { | ||
a.config[k] = fmt.Sprintf("%v", v) | ||
} | ||
} | ||
} | ||
|
||
if configHash == a.lastSavedConfigHash { | ||
fmt.Println("Skipped config JSON save. No changes detected.") | ||
return nil | ||
} | ||
|
||
if err := a.createConfigFileIfMissing(); err != nil { | ||
return err | ||
} | ||
|
||
if err := a.encryptConfig(configJson); err != nil { | ||
return err | ||
} | ||
|
||
a.lastSavedConfigHash = configHash | ||
return nil | ||
} | ||
|
||
// Creates the config file if does not exist and encrypts it. | ||
func (a *AWSKeyVaultStorage) createConfigFileIfMissing() error { | ||
if _, err := os.Stat(a.configFileLocation); !os.IsNotExist(err) { | ||
logger.Infof("Config file already exists at: %s", a.configFileLocation) | ||
return nil | ||
} | ||
|
||
dir := filepath.Dir(a.configFileLocation) | ||
if _, err := os.Stat(dir); os.IsNotExist(err) { | ||
if err := os.MkdirAll(dir, os.ModePerm); err != nil { | ||
return fmt.Errorf("failed to create directory %s: %w", dir, err) | ||
} | ||
} | ||
|
||
if err := a.encryptConfig([]byte("{}")); err != nil { | ||
return err | ||
} | ||
|
||
logger.Infof("Config file created at: %s", a.configFileLocation) | ||
return nil | ||
} | ||
|
||
// Retrieves the details of the KMS key. | ||
func (a *AWSKeyVaultStorage) getKeyDetails() (*kms.DescribeKeyOutput, error) { | ||
keyDetails, err := a.kmsClient.DescribeKey(context.Background(), &kms.DescribeKeyInput{ | ||
KeyId: &a.keyARN, | ||
}) | ||
|
||
if err != nil { | ||
logger.Errorf("Failed to get key details: %v", err) | ||
return nil, fmt.Errorf("failed to get key details: %w", err) | ||
} | ||
|
||
return keyDetails, nil | ||
} | ||
|
||
// createHash creates an MD5 hash of the provided config data. | ||
func (a *AWSKeyVaultStorage) createHash(config []byte) string { | ||
hash := md5.Sum(config) | ||
return hex.EncodeToString(hash[:]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why can't we do |
||
} | ||
|
||
// Retrieves the AWS configuration. | ||
// If the client ID, client secret, and region are provided, it returns the configuration with the provided values else it returns the default configuration. | ||
func getConfig(awsSessionConfig *AWSConfig) (*aws.Config, error) { | ||
if awsSessionConfig.ClientID != "" && awsSessionConfig.ClientSecret != "" && awsSessionConfig.Region != "" { | ||
return &aws.Config{ | ||
Credentials: credentials.NewStaticCredentialsProvider(awsSessionConfig.ClientID, awsSessionConfig.ClientSecret, ""), | ||
Region: awsSessionConfig.Region, | ||
}, nil | ||
} else { | ||
cfg, err := config.LoadDefaultConfig(context.Background()) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to load default config: %w", err) | ||
} | ||
return &cfg, nil | ||
} | ||
} | ||
|
||
// Encrypts the configuration data and writes it to the config file. | ||
func (a *AWSKeyVaultStorage) encryptConfig(config []byte) error { | ||
keydata, err := a.getKeyDetails() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var blob []byte | ||
if keydata.KeyMetadata.KeySpec == types.KeySpecSymmetricDefault { | ||
blob, err = encryptSymmetric(a.kmsClient, a.keyARN, config) | ||
if err != nil { | ||
return fmt.Errorf("failed to encrypt config: %w", err) | ||
} | ||
} else { | ||
blob, err = encryptAsymmetric(a.kmsClient, a.keyARN, config) | ||
if err != nil { | ||
return fmt.Errorf("failed to encrypt config: %w", err) | ||
} | ||
} | ||
|
||
if err := os.WriteFile(a.configFileLocation, blob, 0644); err != nil { | ||
return fmt.Errorf("failed to write config file %s: %w", a.configFileLocation, err) | ||
} | ||
|
||
logger.Debug("Config file created at: ", a.configFileLocation) | ||
return nil | ||
} | ||
|
||
// Changes the KMS key used for encryption and decryption. | ||
func (a *AWSKeyVaultStorage) ChangeKey(newKeyARN string) (bool, error) { | ||
oldKeyARN := a.keyARN | ||
oldKMSClient := a.kmsClient | ||
config, err := getConfig(a.awsConfig) | ||
if err != nil { | ||
return false, fmt.Errorf("failed to get config: %w", err) | ||
} | ||
|
||
client := kms.NewFromConfig(*config) | ||
a.kmsClient = client | ||
a.keyARN = newKeyARN | ||
if err := a.saveConfig(a.config); err != nil { | ||
a.kmsClient = oldKMSClient | ||
a.keyARN = oldKeyARN | ||
logger.Errorf("Failed to change the key to '%s' for config '%s': %v", newKeyARN, a.configFileLocation, err) | ||
return false, fmt.Errorf("failed to change the key for %s: %w", a.configFileLocation, err) | ||
} | ||
|
||
return true, nil | ||
} |
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,53 @@ | ||
**AWS Key Management** | ||
|
||
Protect Secrets Manager connection details with AWS Key Management | ||
|
||
Keeper Secrets Manager integrates with AWS Key Management in order to provide protection for Keeper Secrets Manager configuration files. With this integration, you can protect connection details on your machine while taking advantage of Keeper's zero-knowledge encryption of all your secret credentials. | ||
Features | ||
|
||
* Encrypt and Decrypt your Keeper Secrets Manager configuration files with AWS Key Management | ||
* Protect against unauthorized access to your Secrets Manager connections | ||
* Requires only minor changes to code for immediate protection. Works with all Keeper Secrets Manager Go-Lang SDK functionality | ||
|
||
Prerequisites | ||
|
||
* Supports the Go-Lang Secrets Manager SDK. | ||
* Requires AWS packages: aws, config, credentials, kms, kms-types | ||
* Works with AES/RSA key types with `Encrypt` and `Decrypt` permissions. | ||
|
||
Setup | ||
1. Install Secret-Manager-Go Package | ||
|
||
The Secrets Manager AWS package are located in the Keeper Secrets Manager storage package which can be installed using | ||
|
||
> `go get github.com/keeper-security/secrets-manager-go/core` | ||
Configure AWS Connection | ||
|
||
configuration variables can be provided as | ||
|
||
``` | ||
import awskv "github.com/keeper-security/secrets-manager-go/awskv" | ||
|
||
clientOptions := &ksm.ClientOptions{ | ||
Token: "[One Time Access Token]", | ||
Config: awskv.NewAWSKeyValueStorage(<config-file-path-with-its-name>, <key-arn>, &awskv.AWSConfig{ | ||
ClientID: "<Some Client ID>", | ||
ClientSecret: "<Some Client Secret>", | ||
Region: "<Cloud Region>", | ||
}), | ||
} | ||
``` | ||
The storage will require an AWS credentials if not present it will fetch from environment, as well Secrets Manager configuration which will be encrypted by AWS Key Management. | ||
|
||
Provide `ClientID` , `ClientSecret` and `Region` variables. | ||
|
||
KeyURL must be like this `arn:<partition>:kms:<region>:<account-id>:key/<key-id>` | ||
|
||
For more information about URL see the AWS Key Management Documentation | ||
https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html | ||
|
||
You're ready to use the KSM integration 👍 | ||
|
||
Using the AWS Key Management Integration | ||
|
||
Review the SDK usage. Refer to the SDK (documentation) [https://docs.keeper.io/en/privileged-access-manager/secrets-manager/developer-sdk-library/golang-sdk#retrieve-secrets]. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add the comment in code wherever needed like what the method do see here
https://github.com/Keeper-Security/secrets-manager-go/blob/master/core/core.go
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the comments.