Skip to content

Commit

Permalink
Add IAM authenticator support using local AWS config
Browse files Browse the repository at this point in the history
  • Loading branch information
gl-johnson committed Jul 28, 2023
1 parent b698692 commit 74d41ee
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 4 deletions.
84 changes: 82 additions & 2 deletions conjurapi/authn.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package conjurapi

import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"time"

v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/cyberark/conjur-api-go/conjurapi/authn"
"github.com/cyberark/conjur-api-go/conjurapi/logging"
"github.com/cyberark/conjur-api-go/conjurapi/response"
Expand All @@ -23,8 +28,8 @@ type OidcProvider struct {
}

func (c *Client) RefreshToken() (err error) {
// Fetch cached conjur access token if using OIDC
if c.GetConfig().AuthnType == "oidc" {
// Fetch cached conjur access token if using OIDC or IAM
if c.GetConfig().AuthnType == "oidc" || c.GetConfig().AuthnType == "iam" {
token := c.readCachedAccessToken()
if token != nil {
c.authToken = token
Expand Down Expand Up @@ -251,6 +256,81 @@ func (c *Client) OidcAuthenticate(code, nonce, code_verifier string) ([]byte, er
return resp, err
}

func (c *Client) IAMAuthenticate() ([]byte, error) {
signedHeaders, err := c.IAMAuthenticateHeaders()
if err != nil {
return nil, err
}

req, err := c.IAMAuthenticateRequest(signedHeaders)
if err != nil {
return nil, err
}

res, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}

resp, err := response.DataResponse(res)

if err == nil && c.storage != nil {
c.storage.StoreAuthnToken(resp)
}

return resp, err
}

func (c *Client) IAMAuthenticateHeaders() ([]byte, error) {
ctx := context.TODO()
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
fmt.Println("Error loading AWS config:", err)
return nil, err
}

creds, err := cfg.Credentials.Retrieve(ctx)
if err != nil {
fmt.Println("Error loading AWS credentials:", err)
return nil, err
}

signer := v4.NewSigner()

stsEndpoint := fmt.Sprintf("https://sts.%s.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15", cfg.Region)

request, err := http.NewRequest(http.MethodGet, stsEndpoint, nil)
if err != nil {
fmt.Println("Error:", err)
return nil, err
}

request.Header.Set("Host", request.Host)

// Sign the request
// NOTE: The random string is a hash of an empty payload which is necessary for the correct signature
err = signer.SignHTTP(ctx, creds, request, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "sts", cfg.Region, time.Now().UTC())
if err != nil {
fmt.Println("Error:", err)
return nil, err
}

headerMap := make(map[string]interface{})
for key, values := range request.Header {
if len(values) == 1 {
headerMap[key] = values[0]
}
}

jsonData, err := json.Marshal(headerMap)
if err != nil {
fmt.Println("Error:", err)
return nil, err
}

return jsonData, nil
}

func (c *Client) ListOidcProviders() ([]OidcProvider, error) {
req, err := c.ListOidcProvidersRequest()
if err != nil {
Expand Down
13 changes: 13 additions & 0 deletions conjurapi/authn/iam_authenticator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package authn

type IAMAuthenticator struct {
Authenticate func() ([]byte, error)
}

func (a *IAMAuthenticator) RefreshToken() ([]byte, error) {
return a.Authenticate()
}

func (a *IAMAuthenticator) NeedsTokenRefresh() bool {
return false
}
45 changes: 45 additions & 0 deletions conjurapi/client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package conjurapi

import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
Expand Down Expand Up @@ -66,6 +67,18 @@ func NewClientFromOidcCode(config Config, code, nonce, code_verifier string) (*C
return client, err
}

func NewClientFromAWSCredentials(config Config) (*Client, error) {
authenticator := &authn.IAMAuthenticator{}
client, err := newClientWithAuthenticator(
config,
authenticator,
)
if err == nil {
authenticator.Authenticate = client.IAMAuthenticate
}
return client, err
}

// ReadResponseBody fully reads a response and closes it.
func ReadResponseBody(response io.ReadCloser) ([]byte, error) {
defer response.Close()
Expand Down Expand Up @@ -191,6 +204,10 @@ func newClientFromStoredCredentials(config Config) (*Client, error) {
return newClientFromStoredOidcCredentials(config)
}

if config.AuthnType == "iam" {
return newClientFromStoredAWSConfig(config)
}

// Attempt to load credentials from whatever storage provider is configured
if storageProvider, _ := createStorageProvider(config); storageProvider != nil {
login, password, err := storageProvider.ReadCredentials()
Expand All @@ -217,6 +234,23 @@ func newClientFromStoredOidcCredentials(config Config) (*Client, error) {
return nil, fmt.Errorf("No valid OIDC token found. Please login again.")
}

func newClientFromStoredAWSConfig(config Config) (*Client, error) {
client, err := NewClientFromAWSCredentials(config)
if err != nil {
return nil, err
}

// RefreshToken() will first check for a cached token
// If not found it will go through the authenticator
err = client.RefreshToken()
if err != nil {
return nil, err
}

return client, nil

}

func (c *Client) GetAuthenticator() Authenticator {
return c.authenticator
}
Expand Down Expand Up @@ -300,6 +334,17 @@ func (c *Client) OidcAuthenticateRequest(code, nonce, code_verifier string) (*ht
return req, nil
}

func (c *Client) IAMAuthenticateRequest(signedHeaders []byte) (*http.Request, error) {
authenticateURL := makeRouterURL(c.authnURL(), url.QueryEscape("host/"+c.config.HostID), "authenticate").String()

req, err := http.NewRequest("POST", authenticateURL, bytes.NewBuffer(signedHeaders))
if err != nil {
return nil, err
}

return req, nil
}

// RotateAPIKeyRequest requires roleID argument to be at least partially-qualified
// ID of from [<account>:]<kind>:<identifier>.
func (c *Client) RotateAPIKeyRequest(roleID string) (*http.Request, error) {
Expand Down
16 changes: 14 additions & 2 deletions conjurapi/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/cyberark/conjur-api-go/conjurapi/logging"
)

var supportedAuthnTypes = []string{"authn", "ldap", "oidc"}
var supportedAuthnTypes = []string{"authn", "ldap", "oidc", "iam"}

type Config struct {
Account string `yaml:"account,omitempty"`
Expand All @@ -23,6 +23,7 @@ type Config struct {
SSLCertPath string `yaml:"cert_file,omitempty"`
AuthnType string `yaml:"authn_type,omitempty"`
ServiceID string `yaml:"service_id,omitempty"`
HostID string `yaml:"host_id,omitempty"`
CredentialStorage string `yaml:"credential_storage,omitempty"`
}

Expand All @@ -45,10 +46,14 @@ func (c *Config) Validate() error {
errors = append(errors, fmt.Sprintf("AuthnType must be one of %v", supportedAuthnTypes))
}

if (c.AuthnType == "ldap" || c.AuthnType == "oidc") && c.ServiceID == "" {
if (c.AuthnType == "ldap" || c.AuthnType == "oidc" || c.AuthnType == "iam") && c.ServiceID == "" {
errors = append(errors, fmt.Sprintf("Must specify a ServiceID when using %s", c.AuthnType))
}

if c.AuthnType == "iam" && c.HostID == "" {
errors = append(errors, fmt.Sprintf("Must specify a HostID when using %s", c.AuthnType))
}

if len(errors) == 0 {
return nil
} else if logging.ApiLog.Level == logrus.DebugLevel {
Expand Down Expand Up @@ -194,6 +199,13 @@ func LoadConfig() (Config, error) {
return config, nil
}

func ConfigFromAWSCredentials() (Config, error) {
config := Config{}

logging.ApiLog.Debugf("Final config: %+v\n", config)
return config, nil
}

func getSystemPath() string {
if runtime.GOOS == "windows" {
//No way to use SHGetKnownFolderPath()
Expand Down
12 changes: 12 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module github.com/cyberark/conjur-api-go
go 1.18

require (
github.com/aws/aws-sdk-go-v2 v1.19.1
github.com/aws/aws-sdk-go-v2/config v1.18.30
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.2
Expand All @@ -12,6 +14,16 @@ require (

require (
github.com/alessio/shellescape v1.4.1 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.29 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.36 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.30 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.37 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.30 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.14 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.14 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.20.1 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/danieljoos/wincred v1.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
Expand Down
29 changes: 29 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
github.com/aws/aws-sdk-go-v2 v1.19.1 h1:STs0lbbpXu3byTPcnRLghs2DH0yk9qKDo27TyyJSKsM=
github.com/aws/aws-sdk-go-v2 v1.19.1/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2/config v1.18.30 h1:TTAXQIn31qYFUQjkW6siVrRTX1ux+sADZDOe3jsZcMg=
github.com/aws/aws-sdk-go-v2/config v1.18.30/go.mod h1:+YogjT7e/t9JVu/sOnZZgxTge1G+bPNk8zOaI0QIQvE=
github.com/aws/aws-sdk-go-v2/credentials v1.13.29 h1:KNgCpThGuZyCjq9EuuqoLDenKKMwO/x1Xx01ckDa7VI=
github.com/aws/aws-sdk-go-v2/credentials v1.13.29/go.mod h1:VMq1LcmSEa9qxBlOCYTjVuGJWEEzhGmgL552jQsmhss=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.6 h1:kortK122LvTU34CGX/F9oJpelXKkEA2j/MW48II+8+8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.6/go.mod h1:k7IPHyHNIASI0m0RwOmCjWOTtgG+J0raqwuHH8WhWJE=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.36 h1:kbk81RlPoC6e4co7cQx2FAvH9TgbzxIqCqiosAFiB+w=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.36/go.mod h1:T8Jsn/uNL/AFOXrVYQ1YQaN1r9gN34JU1855/Lyjv+o=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.30 h1:lMl8S5SB8jNCB+Sty2Em4lnu3IJytceHQd7qbmfqKL0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.30/go.mod h1:v3GSCnFxbHzt9dlWBqvA1K1f9lmWuf4ztupZBCAIVs4=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.37 h1:BXiqvN7WuV/pMhz8CivhO8cG8icJcjnjHumif4ukQ0c=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.37/go.mod h1:d4GZ62cjnz/hjKFdAu11gAwK73bdhqaFv2O4J1gaqIs=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.30 h1:UcVZxLVNY4yayCmiG94Ge3l2qbc5WEB/oa4RmjoQEi0=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.30/go.mod h1:wPffyJiWWtHwvpFyn23WjAjVjMnlQOQrl02+vutBh3Y=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.14 h1:gUjz7trfz9qBm0AlkKTvJHBXELi1wvw+2LA9GfD2AsM=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.14/go.mod h1:9kfRdJgLCbnyeqZ/DpaSwcgj9ZDYLfRpe8Sze+NrYfQ=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.14 h1:8bEtxV5UT9ucdWGXfZ7CM3caQhSHGjWnTHt0OeF7m7s=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.14/go.mod h1:nd9BG2UnexN2sDx/mk2Jd6pf3d2E61AiA8m8Fdvdx8Y=
github.com/aws/aws-sdk-go-v2/service/sts v1.20.1 h1:U7h9CPoyMfVoN5jUglB0LglCMP10AK4vMBsbsCKM8Yw=
github.com/aws/aws-sdk-go-v2/service/sts v1.20.1/go.mod h1:BUHusg4cOA1TFGegj7x8/eoWrbdHzJfoMrXcbMQAG0k=
github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
Expand All @@ -9,6 +33,10 @@ 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/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
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=
Expand All @@ -33,6 +61,7 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down

0 comments on commit 74d41ee

Please sign in to comment.