Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IAM authenticator #178

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method Client.IAMAuthenticateHeaders has 6 return statements (exceeds 4 allowed).

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