Skip to content

Commit

Permalink
Start hacking on AWS credentials file compatible mode
Browse files Browse the repository at this point in the history
Signed-off-by: Noah Stride <noah.stride@goteleport.com>
  • Loading branch information
strideynet committed Nov 28, 2024
1 parent 9d2957c commit c148711
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 4 deletions.
42 changes: 38 additions & 4 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ func newRootCmd() (*cobra.Command, error) {
}
rootCmd.AddCommand(x509CredentialProcessCmd)

oneShotCredentialWriteCmd, err := newOneShotCredentialWrite()
oneShotCredentialWriteCmd, err := newX509CredentialFileCmd()
if err != nil {
return nil, fmt.Errorf("initializing one-shot-credential-write command: %w", err)
return nil, fmt.Errorf("initializing x509-credential-file command: %w", err)
}
rootCmd.AddCommand(oneShotCredentialWriteCmd)

Expand Down Expand Up @@ -89,10 +89,10 @@ func (f *sharedFlags) addFlags(cmd *cobra.Command) error {
return nil
}

func newOneShotCredentialWrite() (*cobra.Command, error) {
func newX509CredentialFileCmd() (*cobra.Command, error) {
sf := &sharedFlags{}
cmd := &cobra.Command{
Use: "x509-one-shot-credential-write",
Use: "x509-credential-file",
Short: ``,
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
Expand All @@ -109,6 +109,40 @@ func newOneShotCredentialWrite() (*cobra.Command, error) {
if err != nil {
return fmt.Errorf("fetching x509 context: %w", err)
}
svid := x509Ctx.DefaultSVID()
slog.Debug(
"Fetched X509 SVID",
slog.Group("svid",
"spiffe_id", svid.ID,
"hint", svid.Hint,
),
)

signer := &awsspiffe.X509SVIDSigner{
SVID: svid,
}
signatureAlgorithm, err := signer.SignatureAlgorithm()
if err != nil {
return fmt.Errorf("getting signature algorithm: %w", err)
}
credentials, err := vendoredaws.GenerateCredentials(&vendoredaws.CredentialsOpts{
RoleArn: sf.roleARN,
ProfileArnStr: sf.profileARN,
Region: sf.region,
RoleSessionName: sf.roleSessionName,
TrustAnchorArnStr: sf.trustAnchorARN,
SessionDuration: sf.sessionDuration,
}, signer, signatureAlgorithm)
if err != nil {
return fmt.Errorf("generating credentials: %w", err)
}
slog.Debug(
"Generated AWS credentials",
"expiration", credentials.Expiration,
)

// Now we write this to disk in the format that the AWS CLI/SDK
// expects for a credentials file.
},

Check failure on line 146 in cmd/main.go

View workflow job for this annotation

GitHub Actions / lint

missing return (typecheck)
// Hidden for now as the daemon is likely more "usable"
Hidden: true,
Expand Down
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ require (
require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/aws/aws-sdk-go v1.55.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/zeebo/errs v1.3.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/net v0.30.0 // indirect
Expand All @@ -26,4 +30,6 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
google.golang.org/grpc v1.67.1 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
Expand All @@ -53,6 +55,8 @@ google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFN
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
Expand Down
74 changes: 74 additions & 0 deletions internal/aws_credentials_file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package internal

import (
"fmt"
"log/slog"
"os"

"gopkg.in/ini.v1"
)

type AWSCredentialsFileConfig struct {
Path string
ProfileName string
Force bool
ReplaceFile bool
}

type AWSCredentialsFileProfile struct {
AWSAccessKeyID string
AWSSecretAccessKey string
AWSSessionToken string
}

func (p AWSCredentialsFileProfile) Validate() error {
// TODO: Validate
return nil
}

// UpsertAWSCredentialsFileProfile writes the provided AWS credentials profile to the AWS credentials file.
// See https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-files.html
func UpsertAWSCredentialsFileProfile(
log *slog.Logger,
cfg AWSCredentialsFileConfig,
p AWSCredentialsFileProfile,
) error {
if err := p.Validate(); err != nil {
return fmt.Errorf("validating aws credentials file profile: %w", err)
}

f, err := ini.Load(cfg.Path)
if err != nil {
if !os.IsNotExist(err) {
if !cfg.Force {
log.Error(
"When loading the existing AWS credentials file, an error occurred. Use --force to ignore errors and attempt to overwrite.",
"error", err,
"path", cfg.Path,
)
return fmt.Errorf("loading existing aws credentials file: %w", err)
}
log.Warn(
"When loading the existing AWS credentials file, an error occurred. As --force is set, the file will be overwritten.",
"error", err,
"path", cfg.Path,
)
}
f = ini.Empty()
}

sectionName := "default"
if cfg.ProfileName != "" {
sectionName = cfg.ProfileName
}
sec := f.Section(sectionName)

sec.Key("aws_secret_access_key").SetValue(p.AWSSecretAccessKey)
sec.Key("aws_access_key_id").SetValue(p.AWSAccessKeyID)
sec.Key("aws_session_token").SetValue(p.AWSSessionToken)

if err := f.SaveTo(cfg.Path); err != nil {
return fmt.Errorf("saving aws credentials file: %w", err)
}
return nil
}
69 changes: 69 additions & 0 deletions internal/aws_credentials_file_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package internal

import (
"log/slog"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
)

func TestAWSCredentialsFile_Write(t *testing.T) {
// TODO: Add more cases:
// - If file exists, but is a bad ini and Force moe
// - Replace mode
tmp := t.TempDir()
configPath := filepath.Join(tmp, "config")
log := slog.Default()

err := UpsertAWSCredentialsFileProfile(
log,
AWSCredentialsFileConfig{
Path: configPath,
},
AWSCredentialsFileProfile{
AWSAccessKeyID: "1234567890",
AWSSecretAccessKey: "abcdefgh",
AWSSessionToken: "ijklmnop",
},
)
require.NoError(t, err)

got, err := os.ReadFile(configPath)
require.NoError(t, err)

require.Equal(t, `[default]
aws_secret_access_key = abcdefgh
aws_access_key_id = 1234567890
aws_session_token = ijklmnop
`, string(got))

t.Run("bad file", func(t *testing.T) {
tmp := t.TempDir()
configPath := filepath.Join(tmp, "config")
require.NoError(t, os.WriteFile(configPath, []byte("bad ini"), 0600))
err := UpsertAWSCredentialsFileProfile(
log,
AWSCredentialsFileConfig{
Path: configPath,
Force: true,
},
AWSCredentialsFileProfile{
AWSAccessKeyID: "1234567890",
AWSSecretAccessKey: "abcdefgh",
AWSSessionToken: "ijklmnop",
},
)
require.NoError(t, err)

got, err := os.ReadFile(configPath)
require.NoError(t, err)

require.Equal(t, `[default]
aws_secret_access_key = abcdefgh
aws_access_key_id = 1234567890
aws_session_token = ijklmnop
`, string(got))
})
}

0 comments on commit c148711

Please sign in to comment.