From 8c63a1044d85808c6dad6d3300e8337507decdfe Mon Sep 17 00:00:00 2001 From: Brad Sickles Date: Wed, 11 Feb 2026 18:42:10 -0500 Subject: [PATCH 01/12] skeleton --- access/aws/resolve.go | 17 ++++++--- access/gcp/assumer.go | 26 +++++++++++++ builtin/aws/aws-account/coster.go | 3 +- builtin/aws/aws-account/scanner.go | 5 ++- builtin/aws/secret_manager.go | 33 ++++++++++++++++ builtin/discover_coster.go | 6 ++- builtin/discover_secret_manager.go | 45 ++++++++++++++++++++++ builtin/gcp/secret_manager.go | 34 +++++++++++++++++ builtin/scanner_creator.go | 2 +- go.mod | 26 ++++++++----- go.sum | 61 +++++++++++++++++++----------- secret_manager.go | 46 ++++++++++++++++++++++ 12 files changed, 259 insertions(+), 45 deletions(-) create mode 100644 access/gcp/assumer.go create mode 100644 builtin/aws/secret_manager.go create mode 100644 builtin/discover_secret_manager.go create mode 100644 builtin/gcp/secret_manager.go create mode 100644 secret_manager.go diff --git a/access/aws/resolve.go b/access/aws/resolve.go index 01bee31..832cb76 100644 --- a/access/aws/resolve.go +++ b/access/aws/resolve.go @@ -3,12 +3,17 @@ package aws import ( "encoding/json" "fmt" + "time" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/credentials/stscreds" "github.com/aws/aws-sdk-go-v2/service/sts" "gopkg.in/nullstone-io/go-api-client.v0/types" - "time" +) + +const ( + DefaultAwsRegion = "us-east-1" ) var ( @@ -16,10 +21,12 @@ var ( assumeRoleDuration = time.Hour ) -func ResolveConfig(assumerAwsConfig aws.Config, provider types.Provider, cfg types.ProviderConfig) (aws.Config, error) { - region := "us-east-1" - if cfg.Aws != nil && cfg.Aws.Region != "" { - region = cfg.Aws.Region +func ResolveConfig(assumerAwsConfig aws.Config, provider types.Provider, cfg *types.AwsProviderConfig, region string) (aws.Config, error) { + if region == "" { + region = DefaultAwsRegion + } + if cfg != nil && cfg.Region != "" { + region = cfg.Region } awsConfig := aws.Config{Region: region} diff --git a/access/gcp/assumer.go b/access/gcp/assumer.go new file mode 100644 index 0000000..9e52884 --- /dev/null +++ b/access/gcp/assumer.go @@ -0,0 +1,26 @@ +package gcp + +import ( + "context" + "encoding/base64" + "fmt" + + "golang.org/x/oauth2/google" +) + +type Assumer struct { + Credentials *google.Credentials +} + +func AssumerFromBase64KeyFile(ctx context.Context, encoded string) (Assumer, error) { + base64Decoded, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + return Assumer{}, fmt.Errorf("error decoding gcp service account key: %w", err) + } + + creds, err := google.CredentialsFromJSONWithType(ctx, base64Decoded, google.ServiceAccount, "https://www.googleapis.com/auth/cloud-platform") + if err != nil { + return Assumer{}, fmt.Errorf("error loading credentials from json: %w", err) + } + return Assumer{Credentials: creds}, nil +} diff --git a/builtin/aws/aws-account/coster.go b/builtin/aws/aws-account/coster.go index 707f08d..3ea29d2 100644 --- a/builtin/aws/aws-account/coster.go +++ b/builtin/aws/aws-account/coster.go @@ -26,8 +26,7 @@ type Coster struct { func (c Coster) GetCosts(ctx context.Context, query infra_sdk.CostQuery) (*infra_sdk.CostResult, error) { // Cost Explorer is global, use us-east-1 as the region to satisfy the aws sdk - providerConfig := types.ProviderConfig{Aws: &types.AwsProviderConfig{Region: "us-east-1"}} - awsConfig, err := aws.ResolveConfig(c.Assumer.AwsConfig(), c.Provider, providerConfig) + awsConfig, err := aws.ResolveConfig(c.Assumer.AwsConfig(), c.Provider, &types.AwsProviderConfig{Region: "us-east-1"}, "") if err != nil { return nil, fmt.Errorf("error resolving aws config: %w", err) } diff --git a/builtin/aws/aws-account/scanner.go b/builtin/aws/aws-account/scanner.go index ed8b1d5..4470314 100644 --- a/builtin/aws/aws-account/scanner.go +++ b/builtin/aws/aws-account/scanner.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/nullstone-io/infra-sdk" "github.com/nullstone-io/infra-sdk/access/aws" "gopkg.in/nullstone-io/go-api-client.v0/types" @@ -41,11 +42,11 @@ var ( type Scanner struct { Assumer aws.Assumer Provider types.Provider - ProviderConfig types.ProviderConfig + ProviderConfig *types.AwsProviderConfig } func (s Scanner) Scan(ctx context.Context) ([]infra_sdk.ScanResource, error) { - awsConfig, err := aws.ResolveConfig(s.Assumer.AwsConfig(), s.Provider, s.ProviderConfig) + awsConfig, err := aws.ResolveConfig(s.Assumer.AwsConfig(), s.Provider, s.ProviderConfig, "") if err != nil { return nil, fmt.Errorf("error resolving aws config: %w", err) } diff --git a/builtin/aws/secret_manager.go b/builtin/aws/secret_manager.go new file mode 100644 index 0000000..1127c83 --- /dev/null +++ b/builtin/aws/secret_manager.go @@ -0,0 +1,33 @@ +package aws + +import ( + "context" + + infra_sdk "github.com/nullstone-io/infra-sdk" + "github.com/nullstone-io/infra-sdk/access/aws" + "gopkg.in/nullstone-io/go-api-client.v0/types" +) + +var ( + _ infra_sdk.SecretManager = SecretManager{} +) + +type SecretManager struct { + Assumer aws.Assumer + Provider types.Provider +} + +func (s SecretManager) List(ctx context.Context, location types.SecretLocation) ([]types.Secret, error) { + //TODO implement me + panic("implement me") +} + +func (s SecretManager) Create(ctx context.Context, identity types.SecretIdentity, value string) (*types.Secret, error) { + //TODO implement me + panic("implement me") +} + +func (s SecretManager) Update(ctx context.Context, identity types.SecretIdentity, value string) (*types.Secret, error) { + //TODO implement me + panic("implement me") +} diff --git a/builtin/discover_coster.go b/builtin/discover_coster.go index 9182a48..3bbcf9e 100644 --- a/builtin/discover_coster.go +++ b/builtin/discover_coster.go @@ -2,13 +2,15 @@ package builtin import ( infra_sdk "github.com/nullstone-io/infra-sdk" - "github.com/nullstone-io/infra-sdk/access/aws" + aws_access "github.com/nullstone-io/infra-sdk/access/aws" + gcp_access "github.com/nullstone-io/infra-sdk/access/gcp" aws_account "github.com/nullstone-io/infra-sdk/builtin/aws/aws-account" "gopkg.in/nullstone-io/go-api-client.v0/types" ) type CosterCreator struct { - AwsAssumer aws.Assumer + AwsAssumer aws_access.Assumer + GcpAssumer gcp_access.Assumer } func (s CosterCreator) NewMultiCoster(providers []types.Provider) (infra_sdk.MultiCoster, error) { diff --git a/builtin/discover_secret_manager.go b/builtin/discover_secret_manager.go new file mode 100644 index 0000000..c1daf98 --- /dev/null +++ b/builtin/discover_secret_manager.go @@ -0,0 +1,45 @@ +package builtin + +import ( + infra_sdk "github.com/nullstone-io/infra-sdk" + aws_access "github.com/nullstone-io/infra-sdk/access/aws" + gcp_access "github.com/nullstone-io/infra-sdk/access/gcp" + "github.com/nullstone-io/infra-sdk/builtin/aws" + "github.com/nullstone-io/infra-sdk/builtin/gcp" + "gopkg.in/nullstone-io/go-api-client.v0/types" +) + +type SecretManagerCreator struct { + AwsAssumer aws_access.Assumer + GcpAssumer gcp_access.Assumer +} + +func (c SecretManagerCreator) NewSecretManager(providers []types.Provider, providerConfig types.ProviderConfig) (infra_sdk.MultiSecretManager, error) { + mc := infra_sdk.MultiSecretManager{Managers: map[string]infra_sdk.SecretManager{}} + for _, cur := range providers { + manager, err := c.DiscoverSecretManager(cur, providerConfig) + if err != nil { + return mc, err + } + mc.Managers[cur.ProviderType] = manager + } + return mc, nil +} + +func (c SecretManagerCreator) DiscoverSecretManager(provider types.Provider, providerConfig types.ProviderConfig) (infra_sdk.SecretManager, error) { + switch provider.ProviderType { + case "aws": + return aws.SecretManager{ + Assumer: c.AwsAssumer, + Provider: provider, + ProviderConfig: providerConfig.Aws, + }, nil + case "gcp": + return gcp.SecretManager{ + Assumer: c.GcpAssumer, + Provider: provider, + ProviderConfig: providerConfig.Gcp, + }, nil + } + return nil, nil +} diff --git a/builtin/gcp/secret_manager.go b/builtin/gcp/secret_manager.go new file mode 100644 index 0000000..59e30b1 --- /dev/null +++ b/builtin/gcp/secret_manager.go @@ -0,0 +1,34 @@ +package gcp + +import ( + "context" + + infra_sdk "github.com/nullstone-io/infra-sdk" + "github.com/nullstone-io/infra-sdk/access/gcp" + "gopkg.in/nullstone-io/go-api-client.v0/types" +) + +var ( + _ infra_sdk.SecretManager = SecretManager{} +) + +type SecretManager struct { + Assumer gcp.Assumer + Provider types.Provider + ProviderConfig *types.GcpProviderConfig +} + +func (s SecretManager) List(ctx context.Context, location types.SecretLocation) ([]types.Secret, error) { + //TODO implement me + panic("implement me") +} + +func (s SecretManager) Create(ctx context.Context, identity types.SecretIdentity, value string) (*types.Secret, error) { + //TODO implement me + panic("implement me") +} + +func (s SecretManager) Update(ctx context.Context, identity types.SecretIdentity, value string) (*types.Secret, error) { + //TODO implement me + panic("implement me") +} diff --git a/builtin/scanner_creator.go b/builtin/scanner_creator.go index a7ba84b..032aadb 100644 --- a/builtin/scanner_creator.go +++ b/builtin/scanner_creator.go @@ -24,7 +24,7 @@ func (s ScannerCreator) NewScanner(ctx context.Context, getProviderFn GetProvide return aws_account.Scanner{ Assumer: s.AwsAssumer, Provider: *provider, - ProviderConfig: providerConfig, + ProviderConfig: providerConfig.Aws, }, nil } } diff --git a/go.mod b/go.mod index e6acfca..2b411f7 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/nullstone-io/infra-sdk go 1.24.0 require ( - github.com/aws/aws-sdk-go-v2 v1.39.4 + github.com/aws/aws-sdk-go-v2 v1.41.1 github.com/aws/aws-sdk-go-v2/credentials v1.18.19 github.com/aws/aws-sdk-go-v2/service/apigateway v1.35.9 github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.32.9 @@ -22,40 +22,46 @@ require ( github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.30.9 github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1 github.com/aws/aws-sdk-go-v2/service/s3 v1.89.0 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.1 github.com/aws/aws-sdk-go-v2/service/sns v1.39.1 github.com/aws/aws-sdk-go-v2/service/sqs v1.42.11 github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 github.com/stretchr/testify v1.11.1 - golang.org/x/net v0.46.0 - gopkg.in/nullstone-io/go-api-client.v0 v0.0.0-20251110195425-e7a9dedd8aba + golang.org/x/net v0.48.0 + golang.org/x/oauth2 v0.35.0 + gopkg.in/nullstone-io/go-api-client.v0 v0.0.0-20260211232559-c209a9da78a0 ) require ( + cloud.google.com/go/compute/metadata v0.3.0 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.11 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.2 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.11 // indirect - github.com/aws/smithy-go v1.23.1 // indirect + github.com/aws/smithy-go v1.24.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/hcl/v2 v2.24.0 // indirect github.com/jinzhu/copier v0.4.0 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/nullstone-io/module v0.2.10 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/tmccombs/hcl2json v0.6.8 // indirect github.com/zclconf/go-cty v1.17.0 // indirect - golang.org/x/mod v0.29.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/text v0.30.0 // indirect - golang.org/x/tools v0.38.0 // indirect + golang.org/x/mod v0.32.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/text v0.32.0 // indirect + golang.org/x/tools v0.40.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 7555008..cdcd2f8 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= @@ -7,16 +9,16 @@ github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/ github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= -github.com/aws/aws-sdk-go-v2 v1.39.4 h1:qTsQKcdQPHnfGYBBs+Btl8QwxJeoWcOcPcixK90mRhg= -github.com/aws/aws-sdk-go-v2 v1.39.4/go.mod h1:yWSxrnioGUZ4WVv9TgMrNUeLV3PFESn/v+6T/Su8gnM= +github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= +github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 h1:t9yYsydLYNBk9cJ73rgPhPWqOh/52fcWDQB5b1JsKSY= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2/go.mod h1:IusfVNTmiSN3t4rhxWFaBAqn+mcNdwKtPcV16eYdgko= github.com/aws/aws-sdk-go-v2/credentials v1.18.19 h1:Jc1zzwkSY1QbkEcLujwqRTXOdvW8ppND3jRBb/VhBQc= github.com/aws/aws-sdk-go-v2/credentials v1.18.19/go.mod h1:DIfQ9fAk5H0pGtnqfqkbSIzky82qYnGvh06ASQXXg6A= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 h1:7AANQZkF3ihM8fbdftpjhken0TP9sBzFbV/Ze/Y4HXA= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11/go.mod h1:NTF4QCGkm6fzVwncpkFQqoquQyOolcyXfbpC98urj+c= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 h1:ShdtWUZT37LCAA4Mw2kJAJtzaszfSHFb5n25sdcv4YE= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11/go.mod h1:7bUb2sSr2MZ3M/N+VyETLTQtInemHXb/Fl3s8CLzm0Y= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.11 h1:bKgSxk1TW//00PGQqYmrq83c+2myGidEclp+t9pPqVI= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.11/go.mod h1:vrPYCQ6rFHL8jzQA8ppu3gWX18zxjLIDGTeqDxkBmSI= github.com/aws/aws-sdk-go-v2/service/apigateway v1.35.9 h1:9izRfGPf77V364SWt2NjmR/3VI7AgRAFZaDJHAtD9pI= @@ -61,15 +63,18 @@ github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1 h1:KuoA/cmy/yK8n9v/d6WH36cZ github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1/go.mod h1:BymbICXBfXQHO6i+yTBhocA9a6DM0uMDQqYelqa9wzs= github.com/aws/aws-sdk-go-v2/service/s3 v1.89.0 h1:JbCUlVDEjmhpvpIgXP9QN+/jW61WWWj99cGmxMC49hM= github.com/aws/aws-sdk-go-v2/service/s3 v1.89.0/go.mod h1:UHKgcRSx8PVtvsc1Poxb/Co3PD3wL7P+f49P0+cWtuY= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.1 h1:72DBkm/CCuWx2LMHAXvLDkZfzopT3psfAeyZDIt1/yE= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.1/go.mod h1:A+oSJxFvzgjZWkpM0mXs3RxB5O1SD6473w3qafOC9eU= github.com/aws/aws-sdk-go-v2/service/sns v1.39.1 h1:GKg/7I4IGRrAm0j5Hwxrk8B9LXhzfxyDoDazW5XB7Ew= github.com/aws/aws-sdk-go-v2/service/sns v1.39.1/go.mod h1:jQSGNtQAakrW+5vvUF5398mxsHryy0rsYWUXIQoHDAw= github.com/aws/aws-sdk-go-v2/service/sqs v1.42.11 h1:tt34G790giMoWqpqJOfvc5BD25hHRSjgvx1x1jtwi9w= github.com/aws/aws-sdk-go-v2/service/sqs v1.42.11/go.mod h1:tj8YTswoacIeRGjkYuHOkUd4ioQ4Of0m+gy09kuns9o= github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 h1:Ekml5vGg6sHSZLZJQJagefnVe6PmqC2oiRkBq4F7fU0= github.com/aws/aws-sdk-go-v2/service/sts v1.38.9/go.mod h1:/e15V+o1zFHWdH3u7lpI3rVBcxszktIKuHKCY2/py+k= -github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M= -github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= +github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= @@ -88,19 +93,26 @@ github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQx github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/nullstone-io/module v0.2.10 h1:wCKrlyxyH9XQW5HliW/V6qNsDgUQxUCcWL60Ojlz+2U= github.com/nullstone-io/module v0.2.10/go.mod h1:btQiO0giVWDvvaQ7CLnPmuPPakJc55lAr8OlE1LK6hg= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -126,34 +138,37 @@ github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6 github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/nullstone-io/go-api-client.v0 v0.0.0-20251110195425-e7a9dedd8aba h1:6BIxKc6vSCyHq4gelY2TGZ6MMWSVhjc7DSwva+qGmt8= -gopkg.in/nullstone-io/go-api-client.v0 v0.0.0-20251110195425-e7a9dedd8aba/go.mod h1:Bj01hknD135uWb7j+9EIcE775ZXYv9pWSXdryTHMrhM= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/nullstone-io/go-api-client.v0 v0.0.0-20260211232559-c209a9da78a0 h1:GTSy+hp5ydOtNew44K5PwEXSbTMNOW93BHcl3sazBn8= +gopkg.in/nullstone-io/go-api-client.v0 v0.0.0-20260211232559-c209a9da78a0/go.mod h1:BYEeESmUm0TQ3xzt1tZETiQ13WGNpc+QmuyePYI/S9g= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/secret_manager.go b/secret_manager.go new file mode 100644 index 0000000..b889534 --- /dev/null +++ b/secret_manager.go @@ -0,0 +1,46 @@ +package infra_sdk + +import ( + "context" + "fmt" + + "gopkg.in/nullstone-io/go-api-client.v0/types" +) + +type SecretManager interface { + List(ctx context.Context, location types.SecretLocation) ([]types.Secret, error) + Create(ctx context.Context, identity types.SecretIdentity, value string) (*types.Secret, error) + Update(ctx context.Context, identity types.SecretIdentity, value string) (*types.Secret, error) +} + +var ( + _ SecretManager = MultiSecretManager{} +) + +type MultiSecretManager struct { + Managers map[string]SecretManager +} + +func (m MultiSecretManager) List(ctx context.Context, location types.SecretLocation) ([]types.Secret, error) { + manager, ok := m.Managers[location.Platform] + if !ok { + return nil, fmt.Errorf("secret manager does not support %q platform", location.Platform) + } + return manager.List(ctx, location) +} + +func (m MultiSecretManager) Create(ctx context.Context, identity types.SecretIdentity, value string) (*types.Secret, error) { + manager, ok := m.Managers[identity.Platform] + if !ok { + return nil, fmt.Errorf("secret manager does not support %q platform", identity.Platform) + } + return manager.Create(ctx, identity, value) +} + +func (m MultiSecretManager) Update(ctx context.Context, identity types.SecretIdentity, value string) (*types.Secret, error) { + manager, ok := m.Managers[identity.Platform] + if !ok { + return nil, fmt.Errorf("secret manager does not support %q platform", identity.Platform) + } + return manager.Update(ctx, identity, value) +} From 9ba829afddecab159c395cd4e1290de2bb2105d7 Mon Sep 17 00:00:00 2001 From: Brad Sickles Date: Thu, 12 Feb 2026 11:34:12 -0500 Subject: [PATCH 02/12] Implement aws secret manager --- builtin/aws/secret_manager.go | 127 +++++++++++++++++++++++++++++++--- secret_manager.go | 4 ++ 2 files changed, 123 insertions(+), 8 deletions(-) diff --git a/builtin/aws/secret_manager.go b/builtin/aws/secret_manager.go index 1127c83..e035357 100644 --- a/builtin/aws/secret_manager.go +++ b/builtin/aws/secret_manager.go @@ -2,7 +2,12 @@ package aws import ( "context" + "errors" + "fmt" + "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/aws/aws-sdk-go-v2/service/secretsmanager" + sm_types "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types" infra_sdk "github.com/nullstone-io/infra-sdk" "github.com/nullstone-io/infra-sdk/access/aws" "gopkg.in/nullstone-io/go-api-client.v0/types" @@ -13,21 +18,127 @@ var ( ) type SecretManager struct { - Assumer aws.Assumer - Provider types.Provider + Assumer aws.Assumer + Provider types.Provider + ProviderConfig *types.AwsProviderConfig } func (s SecretManager) List(ctx context.Context, location types.SecretLocation) ([]types.Secret, error) { - //TODO implement me - panic("implement me") + if s.ProviderConfig == nil || s.ProviderConfig.ProviderName == "" { + return nil, nil + } + client, err := s.smClient(location.AwsRegion) + if err != nil { + return nil, err + } + + input := &secretsmanager.ListSecretsInput{} + out, err := client.ListSecrets(ctx, input) + if err != nil { + return nil, fmt.Errorf("error listing secrets: %w", err) + } + result := make([]types.Secret, 0) + for _, cur := range out.SecretList { + result = append(result, types.Secret{ + Identity: s.secretIdentityFromAws(cur.ARN, cur.Name, cur.PrimaryRegion), + Metadata: map[string]any{ + "description": cur.Description, + "tags": cur.Tags, + }, + Value: "", + Redacted: true, + }) + } + return result, nil } func (s SecretManager) Create(ctx context.Context, identity types.SecretIdentity, value string) (*types.Secret, error) { - //TODO implement me - panic("implement me") + if s.ProviderConfig == nil || s.ProviderConfig.ProviderName == "" { + return nil, nil + } + client, err := s.smClient(identity.AwsRegion) + if err != nil { + return nil, err + } + + out, err := client.CreateSecret(ctx, &secretsmanager.CreateSecretInput{ + Name: &identity.Name, + SecretString: &value, + }) + if err != nil { + var ree *sm_types.ResourceExistsException + if errors.As(err, &ree) { + return nil, infra_sdk.ErrSecretAlreadyExists + } + return nil, fmt.Errorf("error creating secret: %w", err) + } + + return &types.Secret{ + Identity: s.secretIdentityFromAws(out.ARN, out.Name, &identity.AwsRegion), + Metadata: nil, + Value: "", + Redacted: false, + }, nil } func (s SecretManager) Update(ctx context.Context, identity types.SecretIdentity, value string) (*types.Secret, error) { - //TODO implement me - panic("implement me") + if s.ProviderConfig == nil || s.ProviderConfig.ProviderName == "" { + return nil, nil + } + client, err := s.smClient(identity.AwsRegion) + if err != nil { + return nil, err + } + + out, err := client.UpdateSecret(ctx, &secretsmanager.UpdateSecretInput{ + SecretId: &identity.Name, + SecretString: &value, + }) + if err != nil { + var rnfe *sm_types.ResourceNotFoundException + if errors.As(err, &rnfe) { + return nil, infra_sdk.ErrDoesNotExist + } + return nil, fmt.Errorf("error updating secret: %w", err) + } + + return &types.Secret{ + Identity: s.secretIdentityFromAws(out.ARN, out.Name, &identity.AwsRegion), + Metadata: nil, + Value: "", + Redacted: false, + }, nil +} + +func (s SecretManager) smClient(region string) (*secretsmanager.Client, error) { + awsConfig, err := aws.ResolveConfig(s.Assumer.AwsConfig(), s.Provider, s.ProviderConfig, region) + if err != nil { + return nil, fmt.Errorf("error resolving aws config: %w", err) + } + return secretsmanager.NewFromConfig(awsConfig), nil +} + +func (s SecretManager) secretIdentityFromAws(secretArn *string, name *string, primaryRegion *string) types.SecretIdentity { + identity := types.SecretIdentity{ + Name: unptr(name), + SecretLocation: types.SecretLocation{ + Platform: types.SecretLocationPlatformAws, + AwsRegion: unptr(primaryRegion), + AwsAccountId: s.Provider.ProviderId, + }, + } + if a, err := arn.Parse(unptr(secretArn)); err == nil { + identity.AwsRegion = a.Region + identity.AwsAccountId = a.AccountID + identity.Name = a.Resource + } + return identity +} + +func unptr[T any](t *T) T { + if t != nil { + return *t + } + var x T + return x } diff --git a/secret_manager.go b/secret_manager.go index b889534..29826f5 100644 --- a/secret_manager.go +++ b/secret_manager.go @@ -2,6 +2,7 @@ package infra_sdk import ( "context" + "errors" "fmt" "gopkg.in/nullstone-io/go-api-client.v0/types" @@ -44,3 +45,6 @@ func (m MultiSecretManager) Update(ctx context.Context, identity types.SecretIde } return manager.Update(ctx, identity, value) } + +var ErrSecretAlreadyExists = errors.New("secret already exists") +var ErrDoesNotExist = errors.New("secret does not exist") From 4a62bd750a121494fed8ce6cb29de074d01ea126 Mon Sep 17 00:00:00 2001 From: Brad Sickles Date: Thu, 12 Feb 2026 12:19:48 -0500 Subject: [PATCH 03/12] Implement gcp secret manager --- access/gcp/resolve.go | 68 +++++++++++++++ builtin/gcp/secret_manager.go | 151 ++++++++++++++++++++++++++++++++-- go.mod | 35 ++++++-- go.sum | 88 +++++++++++++++++--- 4 files changed, 320 insertions(+), 22 deletions(-) create mode 100644 access/gcp/resolve.go diff --git a/access/gcp/resolve.go b/access/gcp/resolve.go new file mode 100644 index 0000000..e1b01cb --- /dev/null +++ b/access/gcp/resolve.go @@ -0,0 +1,68 @@ +package gcp + +import ( + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" + "google.golang.org/api/impersonate" + "google.golang.org/api/option" + "gopkg.in/nullstone-io/go-api-client.v0/types" +) + +var ( + ErrMissingAuthTypeInGcpCredentials = errors.New(`missing "auth_type" in gcp credentials`) + ErrUnsupportedAuthTypeInGcpCredentials = errors.New(`unsupported "auth_type" in gcp credentials`) + ErrMissingEmailInServiceAccountOutput = errors.New(`missing "email" in output for service account`) + ErrMissingPrivateKeyInServiceAccountOutput = errors.New(`missing "private_key" in output for service account`) +) + +func ResolveTokenSource(ctx context.Context, assumer Assumer, provider types.Provider) (oauth2.TokenSource, error) { + gcpCreds := types.GcpCredentials{} + if err := json.Unmarshal(provider.Credentials, &gcpCreds); err != nil { + return nil, fmt.Errorf("invalid gcp credentials: %s", err) + } + + switch gcpCreds.AuthType { + case "": + return nil, ErrMissingAuthTypeInGcpCredentials + case types.GcpAuthTypeServiceAccount: + return ResolveKeyTokenSource(ctx, gcpCreds.ServiceAccountKey, "https://www.googleapis.com/auth/cloud-platform") + case types.GcpAuthTypeServiceAccountImpersonation: + return ResolveImpersonationTokenSource(ctx, assumer, gcpCreds.Impersonation, "https://www.googleapis.com/auth/cloud-platform") + default: + return nil, ErrUnsupportedAuthTypeInGcpCredentials + } +} + +func ResolveKeyTokenSource(ctx context.Context, a types.GcpServiceAccountKey, scopes ...string) (oauth2.TokenSource, error) { + if a.PrivateKey == "" { + return nil, ErrMissingPrivateKeyInServiceAccountOutput + } + + decoded, err := base64.StdEncoding.DecodeString(a.PrivateKey) + if err != nil { + return nil, fmt.Errorf("service account private key is not base64-encoded: %w", err) + } + cfg, err := google.JWTConfigFromJSON(decoded, scopes...) + if err != nil { + return nil, fmt.Errorf("unable to read service account credentials json file: %w", err) + } + return cfg.TokenSource(ctx), nil +} + +func ResolveImpersonationTokenSource(ctx context.Context, assumer Assumer, a types.GcpServiceAccountImpersonation, scopes ...string) (oauth2.TokenSource, error) { + if a.ServiceAccountEmail == "" { + return nil, ErrMissingEmailInServiceAccountOutput + } + + // Create a token source that can impersonate the target service account + return impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{ + TargetPrincipal: a.ServiceAccountEmail, + Scopes: scopes, + }, option.WithTokenSource(assumer.Credentials.TokenSource)) +} diff --git a/builtin/gcp/secret_manager.go b/builtin/gcp/secret_manager.go index 59e30b1..f974e1c 100644 --- a/builtin/gcp/secret_manager.go +++ b/builtin/gcp/secret_manager.go @@ -2,9 +2,18 @@ package gcp import ( "context" + "errors" + "fmt" + "strings" + secretmanager "cloud.google.com/go/secretmanager/apiv1" + "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" infra_sdk "github.com/nullstone-io/infra-sdk" "github.com/nullstone-io/infra-sdk/access/gcp" + "google.golang.org/api/iterator" + "google.golang.org/api/option" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "gopkg.in/nullstone-io/go-api-client.v0/types" ) @@ -19,16 +28,146 @@ type SecretManager struct { } func (s SecretManager) List(ctx context.Context, location types.SecretLocation) ([]types.Secret, error) { - //TODO implement me - panic("implement me") + if s.ProviderConfig == nil || s.ProviderConfig.ProviderName == "" { + return nil, nil + } + client, err := s.smClient(ctx) + if err != nil { + return nil, err + } + defer client.Close() + + parent := fmt.Sprintf("projects/%s", location.GcpProjectId) + it := client.ListSecrets(ctx, &secretmanagerpb.ListSecretsRequest{Parent: parent}) + + result := make([]types.Secret, 0) + for { + secret, err := it.Next() + if errors.Is(err, iterator.Done) { + break + } + if err != nil { + return nil, fmt.Errorf("error listing secrets: %w", err) + } + result = append(result, types.Secret{ + Identity: s.secretIdentityFromGcp(secret.Name), + Metadata: map[string]any{ + "labels": secret.Labels, + }, + Value: "", + Redacted: true, + }) + } + return result, nil } func (s SecretManager) Create(ctx context.Context, identity types.SecretIdentity, value string) (*types.Secret, error) { - //TODO implement me - panic("implement me") + if s.ProviderConfig == nil || s.ProviderConfig.ProviderName == "" { + return nil, nil + } + client, err := s.smClient(ctx) + if err != nil { + return nil, err + } + defer client.Close() + + secret, err := client.CreateSecret(ctx, &secretmanagerpb.CreateSecretRequest{ + Parent: fmt.Sprintf("projects/%s", identity.GcpProjectId), + SecretId: identity.Name, + Secret: &secretmanagerpb.Secret{ + Replication: &secretmanagerpb.Replication{ + Replication: &secretmanagerpb.Replication_Automatic_{ + Automatic: &secretmanagerpb.Replication_Automatic{}, + }, + }, + }, + }) + if err != nil { + if status.Code(err) == codes.AlreadyExists { + return nil, infra_sdk.ErrSecretAlreadyExists + } + return nil, fmt.Errorf("error creating secret: %w", err) + } + + _, err = client.AddSecretVersion(ctx, &secretmanagerpb.AddSecretVersionRequest{ + Parent: secret.Name, + Payload: &secretmanagerpb.SecretPayload{ + Data: []byte(value), + }, + }) + if err != nil { + return nil, fmt.Errorf("error adding secret version: %w", err) + } + + return &types.Secret{ + Identity: identity, + Metadata: nil, + Value: "", + Redacted: false, + }, nil } func (s SecretManager) Update(ctx context.Context, identity types.SecretIdentity, value string) (*types.Secret, error) { - //TODO implement me - panic("implement me") + if s.ProviderConfig == nil || s.ProviderConfig.ProviderName == "" { + return nil, nil + } + client, err := s.smClient(ctx) + if err != nil { + return nil, err + } + defer client.Close() + + _, err = client.AddSecretVersion(ctx, &secretmanagerpb.AddSecretVersionRequest{ + Parent: fmt.Sprintf("projects/%s", identity.GcpProjectId), + Payload: &secretmanagerpb.SecretPayload{ + Data: []byte(value), + }, + }) + if err != nil { + if status.Code(err) == codes.NotFound { + return nil, infra_sdk.ErrDoesNotExist + } + return nil, fmt.Errorf("error updating secret: %w", err) + } + + return &types.Secret{ + Identity: identity, + Metadata: nil, + Value: "", + Redacted: false, + }, nil +} + +func (s SecretManager) smClient(ctx context.Context) (*secretmanager.Client, error) { + tokenSource, err := gcp.ResolveTokenSource(ctx, s.Assumer, s.Provider) + if err != nil { + return nil, fmt.Errorf("error resolving gcp credentials: %w", err) + } + + client, err := secretmanager.NewClient(ctx, option.WithTokenSource(tokenSource)) + if err != nil { + return nil, fmt.Errorf("error creating gcp secret manager client: %w", err) + } + return client, nil +} + +func (s SecretManager) secretIdentityFromGcp(secretName string) types.SecretIdentity { + identity := types.SecretIdentity{ + SecretLocation: types.SecretLocation{ + Platform: types.SecretLocationPlatformGcp, + GcpProjectId: s.Provider.ProviderId, + }, + } + // secretName is one of: + // - "projects/{project}/secrets/{secretId}" + // - "projects/{project}/locations/{location}/secrets/{secretId}" + parts := strings.Split(secretName, "/") + if len(parts) == 4 { + identity.GcpProjectId = parts[1] + identity.Name = parts[len(parts)-1] + } else if len(parts) == 6 { + identity.GcpProjectId = parts[1] + identity.Name = parts[len(parts)-1] + } + return identity } diff --git a/go.mod b/go.mod index 2b411f7..5234810 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/nullstone-io/infra-sdk go 1.24.0 require ( + cloud.google.com/go/secretmanager v1.16.0 github.com/aws/aws-sdk-go-v2 v1.41.1 github.com/aws/aws-sdk-go-v2/credentials v1.18.19 github.com/aws/aws-sdk-go-v2/service/apigateway v1.35.9 @@ -27,13 +28,18 @@ require ( github.com/aws/aws-sdk-go-v2/service/sqs v1.42.11 github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 github.com/stretchr/testify v1.11.1 - golang.org/x/net v0.48.0 + golang.org/x/net v0.49.0 golang.org/x/oauth2 v0.35.0 + google.golang.org/api v0.266.0 + google.golang.org/grpc v1.78.0 gopkg.in/nullstone-io/go-api-client.v0 v0.0.0-20260211232559-c209a9da78a0 ) require ( - cloud.google.com/go/compute/metadata v0.3.0 // indirect + cloud.google.com/go/auth v0.18.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/compute/metadata v0.9.0 // indirect + cloud.google.com/go/iam v1.5.3 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 // indirect @@ -45,23 +51,40 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.11 // indirect github.com/aws/smithy-go v1.24.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/google/go-cmp v0.7.0 // indirect + github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect + github.com/googleapis/gax-go/v2 v2.17.0 // indirect github.com/hashicorp/hcl/v2 v2.24.0 // indirect github.com/jinzhu/copier v0.4.0 // indirect - github.com/kr/pretty v0.3.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/nullstone-io/module v0.2.10 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/tmccombs/hcl2json v0.6.8 // indirect github.com/zclconf/go-cty v1.17.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect + golang.org/x/crypto v0.47.0 // indirect golang.org/x/mod v0.32.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/text v0.32.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.40.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index cdcd2f8..ad5c39f 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,15 @@ -cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= -cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= +cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= +cloud.google.com/go/auth v0.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs= +cloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= +cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= +cloud.google.com/go/secretmanager v1.16.0 h1:19QT7ZsLJ8FSP1k+4esQvuCD7npMJml6hYzilxVyT+k= +cloud.google.com/go/secretmanager v1.16.0/go.mod h1://C/e4I8D26SDTz1f3TQcddhcmiC3rMEl0S1Cakvs3Q= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= @@ -73,28 +83,50 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 h1:Ekml5vGg6sHSZLZJQJagefnVe6Pm github.com/aws/aws-sdk-go-v2/service/sts v1.38.9/go.mod h1:/e15V+o1zFHWdH3u7lpI3rVBcxszktIKuHKCY2/py+k= github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= +github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= +github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= +github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao= +github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= +github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= +github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= github.com/hashicorp/hcl/v2 v2.6.0/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY= github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -107,10 +139,10 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/nullstone-io/module v0.2.10 h1:wCKrlyxyH9XQW5HliW/V6qNsDgUQxUCcWL60Ojlz+2U= github.com/nullstone-io/module v0.2.10/go.mod h1:btQiO0giVWDvvaQ7CLnPmuPPakJc55lAr8OlE1LK6hg= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -136,16 +168,34 @@ github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0 github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -154,15 +204,33 @@ golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/api v0.266.0 h1:hco+oNCf9y7DmLeAtHJi/uBAY7n/7XC9mZPxu1ROiyk= +google.golang.org/api v0.266.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM= +google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= +google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= From a007bd41345855e81ab8db2b1210cfd5a86eb09c Mon Sep 17 00:00:00 2001 From: Brad Sickles Date: Thu, 12 Feb 2026 19:17:43 -0500 Subject: [PATCH 04/12] list from all providers --- secret_manager.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/secret_manager.go b/secret_manager.go index 29826f5..18ce63a 100644 --- a/secret_manager.go +++ b/secret_manager.go @@ -23,11 +23,20 @@ type MultiSecretManager struct { } func (m MultiSecretManager) List(ctx context.Context, location types.SecretLocation) ([]types.Secret, error) { - manager, ok := m.Managers[location.Platform] - if !ok { - return nil, fmt.Errorf("secret manager does not support %q platform", location.Platform) + result := make([]types.Secret, 0) + var errs []error + for _, manager := range m.Managers { + cur, err := manager.List(ctx, location) + if err != nil { + errs = append(errs, err) + } else { + result = append(result, cur...) + } + } + if len(errs) > 0 { + return result, errors.Join(errs...) } - return manager.List(ctx, location) + return result, nil } func (m MultiSecretManager) Create(ctx context.Context, identity types.SecretIdentity, value string) (*types.Secret, error) { From 450532387ecfc434ab3b41b8c328849348631c86 Mon Sep 17 00:00:00 2001 From: Brad Sickles Date: Thu, 12 Feb 2026 19:25:19 -0500 Subject: [PATCH 05/12] fix secret name when listing aws --- builtin/aws/secret_manager.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/builtin/aws/secret_manager.go b/builtin/aws/secret_manager.go index e035357..f9b5432 100644 --- a/builtin/aws/secret_manager.go +++ b/builtin/aws/secret_manager.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strings" "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/aws/aws-sdk-go-v2/service/secretsmanager" @@ -130,7 +131,7 @@ func (s SecretManager) secretIdentityFromAws(secretArn *string, name *string, pr if a, err := arn.Parse(unptr(secretArn)); err == nil { identity.AwsRegion = a.Region identity.AwsAccountId = a.AccountID - identity.Name = a.Resource + identity.Name = strings.TrimPrefix("secret:", a.Resource) } return identity } From 6de932e1d729d8528c095561aafaf4940454f147 Mon Sep 17 00:00:00 2001 From: Brad Sickles Date: Thu, 12 Feb 2026 19:31:38 -0500 Subject: [PATCH 06/12] fix gcp service account access --- access/gcp/resolve.go | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/access/gcp/resolve.go b/access/gcp/resolve.go index e1b01cb..a050e48 100644 --- a/access/gcp/resolve.go +++ b/access/gcp/resolve.go @@ -2,7 +2,6 @@ package gcp import ( "context" - "encoding/base64" "encoding/json" "errors" "fmt" @@ -15,10 +14,9 @@ import ( ) var ( - ErrMissingAuthTypeInGcpCredentials = errors.New(`missing "auth_type" in gcp credentials`) - ErrUnsupportedAuthTypeInGcpCredentials = errors.New(`unsupported "auth_type" in gcp credentials`) - ErrMissingEmailInServiceAccountOutput = errors.New(`missing "email" in output for service account`) - ErrMissingPrivateKeyInServiceAccountOutput = errors.New(`missing "private_key" in output for service account`) + ErrMissingAuthTypeInGcpCredentials = errors.New(`missing "auth_type" in gcp credentials`) + ErrUnsupportedAuthTypeInGcpCredentials = errors.New(`unsupported "auth_type" in gcp credentials`) + ErrMissingEmailInServiceAccountOutput = errors.New(`missing "email" in output for service account`) ) func ResolveTokenSource(ctx context.Context, assumer Assumer, provider types.Provider) (oauth2.TokenSource, error) { @@ -40,15 +38,8 @@ func ResolveTokenSource(ctx context.Context, assumer Assumer, provider types.Pro } func ResolveKeyTokenSource(ctx context.Context, a types.GcpServiceAccountKey, scopes ...string) (oauth2.TokenSource, error) { - if a.PrivateKey == "" { - return nil, ErrMissingPrivateKeyInServiceAccountOutput - } - - decoded, err := base64.StdEncoding.DecodeString(a.PrivateKey) - if err != nil { - return nil, fmt.Errorf("service account private key is not base64-encoded: %w", err) - } - cfg, err := google.JWTConfigFromJSON(decoded, scopes...) + keyFile, _ := json.Marshal(a) + cfg, err := google.JWTConfigFromJSON(keyFile, scopes...) if err != nil { return nil, fmt.Errorf("unable to read service account credentials json file: %w", err) } From 652716e470741aa642474f0e60ff8455afdf7d2d Mon Sep 17 00:00:00 2001 From: Brad Sickles Date: Thu, 12 Feb 2026 19:42:10 -0500 Subject: [PATCH 07/12] add fallback to gcp secrets manager to use provider --- builtin/gcp/secret_manager.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/builtin/gcp/secret_manager.go b/builtin/gcp/secret_manager.go index f974e1c..b3b751a 100644 --- a/builtin/gcp/secret_manager.go +++ b/builtin/gcp/secret_manager.go @@ -37,6 +37,10 @@ func (s SecretManager) List(ctx context.Context, location types.SecretLocation) } defer client.Close() + if location.GcpProjectId == "" { + location.GcpProjectId = s.Provider.ProviderId + } + parent := fmt.Sprintf("projects/%s", location.GcpProjectId) it := client.ListSecrets(ctx, &secretmanagerpb.ListSecretsRequest{Parent: parent}) @@ -71,6 +75,10 @@ func (s SecretManager) Create(ctx context.Context, identity types.SecretIdentity } defer client.Close() + if identity.GcpProjectId == "" { + identity.GcpProjectId = s.Provider.ProviderId + } + secret, err := client.CreateSecret(ctx, &secretmanagerpb.CreateSecretRequest{ Parent: fmt.Sprintf("projects/%s", identity.GcpProjectId), SecretId: identity.Name, @@ -117,6 +125,10 @@ func (s SecretManager) Update(ctx context.Context, identity types.SecretIdentity } defer client.Close() + if identity.GcpProjectId == "" { + identity.GcpProjectId = s.Provider.ProviderId + } + _, err = client.AddSecretVersion(ctx, &secretmanagerpb.AddSecretVersionRequest{ Parent: fmt.Sprintf("projects/%s", identity.GcpProjectId), Payload: &secretmanagerpb.SecretPayload{ From cda3a77be3c67a91e71eb5b6a3707baae68b734c Mon Sep 17 00:00:00 2001 From: Brad Sickles Date: Thu, 12 Feb 2026 19:49:31 -0500 Subject: [PATCH 08/12] allow secrets Create/Update without specifying a platform (uses only cloud account) --- .claude/settings.local.json | 9 +++++++++ secret_manager.go | 31 +++++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..b6bda46 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(go get:*)", + "Bash(go mod tidy:*)", + "Bash(go mod vendor:*)" + ] + } +} diff --git a/secret_manager.go b/secret_manager.go index 18ce63a..dbfc0de 100644 --- a/secret_manager.go +++ b/secret_manager.go @@ -40,19 +40,42 @@ func (m MultiSecretManager) List(ctx context.Context, location types.SecretLocat } func (m MultiSecretManager) Create(ctx context.Context, identity types.SecretIdentity, value string) (*types.Secret, error) { - manager, ok := m.Managers[identity.Platform] - if !ok { - return nil, fmt.Errorf("secret manager does not support %q platform", identity.Platform) + manager, err := m.findManager(identity) + if err != nil { + return nil, err } return manager.Create(ctx, identity, value) } func (m MultiSecretManager) Update(ctx context.Context, identity types.SecretIdentity, value string) (*types.Secret, error) { + manager, err := m.findManager(identity) + if err != nil { + return nil, err + } + return manager.Update(ctx, identity, value) +} + +func (m MultiSecretManager) findManager(identity types.SecretIdentity) (SecretManager, error) { + if len(m.Managers) == 0 { + return nil, fmt.Errorf("no cloud platforms are configured") + } + + if identity.Platform == "" { + if len(m.Managers) > 1 { + return nil, fmt.Errorf("multiple cloud platforms are configured, you must specify a cloud platform") + } + if len(m.Managers) == 1 { + for _, cur := range m.Managers { + return cur, nil + } + } + } + manager, ok := m.Managers[identity.Platform] if !ok { return nil, fmt.Errorf("secret manager does not support %q platform", identity.Platform) } - return manager.Update(ctx, identity, value) + return manager, nil } var ErrSecretAlreadyExists = errors.New("secret already exists") From 008003f04c2815b7b6dae87ab5af0978bc64258d Mon Sep 17 00:00:00 2001 From: Brad Sickles Date: Thu, 12 Feb 2026 19:57:49 -0500 Subject: [PATCH 09/12] coerce gcp secret project id so it is not project number --- builtin/gcp/secret_manager.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/builtin/gcp/secret_manager.go b/builtin/gcp/secret_manager.go index b3b751a..9161ed3 100644 --- a/builtin/gcp/secret_manager.go +++ b/builtin/gcp/secret_manager.go @@ -53,14 +53,17 @@ func (s SecretManager) List(ctx context.Context, location types.SecretLocation) if err != nil { return nil, fmt.Errorf("error listing secrets: %w", err) } - result = append(result, types.Secret{ + cur := types.Secret{ Identity: s.secretIdentityFromGcp(secret.Name), Metadata: map[string]any{ "labels": secret.Labels, }, Value: "", Redacted: true, - }) + } + // The returned secret name contains project number instead of the project id, let's coerce back to project id + cur.Identity.GcpProjectId = location.GcpProjectId + result = append(result, cur) } return result, nil } From d2939074980b8abc443f26d905e48c0d3e76645e Mon Sep 17 00:00:00 2001 From: Brad Sickles Date: Thu, 12 Feb 2026 19:59:57 -0500 Subject: [PATCH 10/12] fix aws secret parse --- builtin/aws/secret_manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/aws/secret_manager.go b/builtin/aws/secret_manager.go index f9b5432..1356100 100644 --- a/builtin/aws/secret_manager.go +++ b/builtin/aws/secret_manager.go @@ -131,7 +131,7 @@ func (s SecretManager) secretIdentityFromAws(secretArn *string, name *string, pr if a, err := arn.Parse(unptr(secretArn)); err == nil { identity.AwsRegion = a.Region identity.AwsAccountId = a.AccountID - identity.Name = strings.TrimPrefix("secret:", a.Resource) + identity.Name = strings.TrimPrefix(a.Resource, "secret:") } return identity } From cdf7d88c7edd91f309d27f42fd7b3d4d6b13eb1f Mon Sep 17 00:00:00 2001 From: Brad Sickles Date: Thu, 12 Feb 2026 23:08:37 -0500 Subject: [PATCH 11/12] update go-api-client --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5234810..68ad9ed 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( golang.org/x/oauth2 v0.35.0 google.golang.org/api v0.266.0 google.golang.org/grpc v1.78.0 - gopkg.in/nullstone-io/go-api-client.v0 v0.0.0-20260211232559-c209a9da78a0 + gopkg.in/nullstone-io/go-api-client.v0 v0.0.0-20260213040708-c776dab7c2ce ) require ( diff --git a/go.sum b/go.sum index ad5c39f..bc3954f 100644 --- a/go.sum +++ b/go.sum @@ -235,8 +235,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/nullstone-io/go-api-client.v0 v0.0.0-20260211232559-c209a9da78a0 h1:GTSy+hp5ydOtNew44K5PwEXSbTMNOW93BHcl3sazBn8= -gopkg.in/nullstone-io/go-api-client.v0 v0.0.0-20260211232559-c209a9da78a0/go.mod h1:BYEeESmUm0TQ3xzt1tZETiQ13WGNpc+QmuyePYI/S9g= +gopkg.in/nullstone-io/go-api-client.v0 v0.0.0-20260213040708-c776dab7c2ce h1:uDIn/tgQXTHs1mgP9PkHqRxf1+lMgb+48bsqqYc0LCY= +gopkg.in/nullstone-io/go-api-client.v0 v0.0.0-20260213040708-c776dab7c2ce/go.mod h1:BYEeESmUm0TQ3xzt1tZETiQ13WGNpc+QmuyePYI/S9g= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From bc9a4810837bee981e90a9ce535df0fb29e4e7d4 Mon Sep 17 00:00:00 2001 From: Brad Sickles Date: Thu, 12 Feb 2026 23:10:07 -0500 Subject: [PATCH 12/12] drop local file --- .claude/settings.local.json | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index b6bda46..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(go get:*)", - "Bash(go mod tidy:*)", - "Bash(go mod vendor:*)" - ] - } -}