Skip to content

Commit

Permalink
feat(IamAssumeAuthenticator): introduce new authenticator type (#229)
Browse files Browse the repository at this point in the history
* feat(IamAssumeAuthenticator): introduce new authenticator type

This commit introduces the new IamAssumeAuthenticator which
will fetch an IAM access token using the IAM getToken operation's
"assume" grant type.  The resulting access token allows the
application to assume the identity of a trusted profile,
similar to the "sudo" feature of Linux.

Signed-off-by: Phil Adams <phil_adams@us.ibm.com>
  • Loading branch information
padamstx authored Oct 15, 2024
1 parent 0bbc175 commit df85f15
Show file tree
Hide file tree
Showing 10 changed files with 1,611 additions and 56 deletions.
234 changes: 191 additions & 43 deletions .secrets.baseline

Large diffs are not rendered by default.

161 changes: 153 additions & 8 deletions Authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
The go-sdk-core project supports the following types of authentication:
- Basic Authentication
- Bearer Token Authentication
- Identity and Access Management (IAM) Authentication
- Identity and Access Management (IAM) Authentication (grant type: apikey)
- Identity and Access Management (IAM) Authentication (grant type: assume)
- Container Authentication
- VPC Instance Authentication
- Cloud Pak for Data Authentication
Expand Down Expand Up @@ -181,7 +182,7 @@ authenticator type is intended for situations in which the application will be m
token itself in terms of initial acquisition and refreshing as needed.


## Identity and Access Management (IAM) Authentication
## Identity and Access Management (IAM) Authentication (grant type: apikey)
The `IamAuthenticator` will accept a user-supplied apikey or refresh token and will perform
the necessary interactions with the IAM token service to obtain a suitable
bearer token for the specified apikey or refresh token. The authenticator will also obtain
Expand Down Expand Up @@ -223,7 +224,7 @@ are optional, but must be specified together.
- Scope: (optional) the scope to be associated with the IAM access token.
If not specified, then no scope will be associated with the access token.

- DisableSSLVerification: (optional) A flag that indicates whether verificaton of the server's SSL
- DisableSSLVerification: (optional) A flag that indicates whether verification of the server's SSL
certificate should be disabled or not. The default value is `false`.

- Headers: (optional) A set of key/value pairs that will be sent as HTTP headers in requests
Expand Down Expand Up @@ -304,6 +305,150 @@ if err != nil {
```


## Identity and Access Management (IAM) Authentication (grant type: assume)
The `IamAssumeAuthenticator` performs a two-step token fetch sequence to obtain
a bearer token that allows the application to assume the identity of a trusted profile:
1. First, the authenticator obtains an initial bearer token using grant type
`urn:ibm:params:oauth:grant-type:apikey`.
This initial token will reflect the identity associated with the input ApiKey.
2. Second, the authenticator uses the grant type `urn:ibm:params:oauth:grant-type:assume` to obtain a bearer token
that reflects the identity of the trusted profile, passing in the initial bearer token
from the first step, along with the trusted profile-related inputs.

The authenticator will also obtain a new bearer token when the current token expires.
The bearer token is then added to each outbound request in the `Authorization` header in the
form:
```
Authorization: Bearer <bearer-token>
```

### Properties

- ApiKey: (required) the IAM apikey to be used to obtain the initial IAM access token.

- IAMProfileCRN: (optional) the Cloud Resource Name (CRN) associated with the trusted profile
for which an access token should be fetched.
Exactly one of IAMProfileCRN, IAMProfileID or IAMProfileName must be specified.

- IAMProfileID: (optional) the ID associated with the trusted profile
for which an access token should be fetched.
Exactly one of IAMProfileCRN, IAMProfileID or IAMProfileName must be specified.

- IAMProfileName: (optional) the name associated with the trusted profile
for which an access token should be fetched. When specifying this property, you must also
specify the IAMAccountID property as well.
Exactly one of IAMProfileCRN, IAMProfileID or IAMProfileName must be specified.

- IAMAccountID: (optional) the ID associated with the IAM account that contains the trusted profile
referenced by the IAMProfileName property. The IAMAccountID property must be specified if and only if
the IAMProfileName property is specified.

- URL: (optional) The base endpoint URL of the IAM token service.
The default value of this property is the "prod" IAM token service endpoint
(`https://iam.cloud.ibm.com`).
Make sure that you use an IAM token service endpoint that is appropriate for the
location of the service being used by your application.
For example, if you are using an instance of a service in the "production" environment
(e.g. `https://resource-controller.cloud.ibm.com`),
then the default "prod" IAM token service endpoint should suffice.
However, if your application is using an instance of a service in the "staging" environment
(e.g. `https://resource-controller.test.cloud.ibm.com`),
then you would also need to configure the authenticator to use the IAM token service "staging"
endpoint as well (`https://iam.test.cloud.ibm.com`).

- ClientId/ClientSecret: (optional) The `ClientId` and `ClientSecret` fields are used to form a
"basic auth" Authorization header for interactions with the IAM token server when fetching the
initial IAM access token. These fields are optional, but must be specified together.

- Scope: (optional) the scope to be used when obtaining the initial IAM access token.
If not specified, then no scope will be associated with the access token.

- DisableSSLVerification: (optional) A flag that indicates whether verification of the server's SSL
certificate should be disabled or not. The default value is `false`.

- Headers: (optional) A set of key/value pairs that will be sent as HTTP headers in requests
made to the IAM token service.

- Client: (Optional) The `http.Client` object used to invoke token service requests. If not specified
by the user, a suitable default Client will be constructed.

### Usage Notes
- The IamAssumeAuthenticator is used to obtain an access token (a bearer token) from the IAM token service
that allows an application to "assume" the identity of a trusted profile.

- The authenticator first uses the ApiKey, URL, ClientId/ClientSecret, Scope, DisableSSLVerification, and Headers
properties to obtain an initial access token by invoking the IAM `getToken`
(grant_type=`urn:ibm:params:oauth:grant-type:apikey`) operation.

- The authenticator then uses the initial access token along with the URL, IAMProfileCRN, IAMProfileID,
IAMProfileName, IAMAccountID, DisableSSLVerification, and Headers properties to obtain an access token by invoking
the IAM `getToken` (grant_type=`urn:ibm:params:oauth:grant-type:assume`) operation.
The access token resulting from this second step will reflect the identity of the specified trusted profile.

- When providing the trusted profile information, you must specify exactly one of: IAMProfileCRN, IAMProfileID
or IAMProfileName. If you specify IAMProfileCRN or IAMProfileID, then the trusted profile must exist in the same account that is
associated with the input ApiKey. If you specify IAMProfileName, then you must also specify the IAMAccountID property
to indicate the IAM account in which the named trusted profile can be found.

### Programming example
```go
import {
"github.com/IBM/go-sdk-core/v5/core"
"<appropriate-git-repo-url>/exampleservicev1"
}
...
// Create the authenticator.
authenticator, err := core.NewIamAssumeAuthenticatorBuilder().
SetApiKey("myapikey").
SetIAMProfileID("myprofile-1").
Build()
if err != nil {
panic(err)
}

// Create the service options struct.
options := &exampleservicev1.ExampleServiceV1Options{
Authenticator: authenticator,
}

// Construct the service instance.
service, err := exampleservicev1.NewExampleServiceV1(options)
if err != nil {
panic(err)
}

// 'service' can now be used to invoke operations.
```

### Configuration example
External configuration:
```
export EXAMPLE_SERVICE_AUTH_TYPE=iamAssume
export EXAMPLE_SERVICE_APIKEY=myapikey
export EXAMPLE_SERVICE_IAM_PROFILE_ID=myprofile-1
```
Application code:
```go
import {
"<appropriate-git-repo-url>/exampleservicev1"
}
...

// Create the service options struct.
options := &exampleservicev1.ExampleServiceV1Options{
ServiceName: "example_service",
}

// Construct the service instance.
service, err := exampleservicev1.NewExampleServiceV1UsingExternalConfig(options)
if err != nil {
panic(err)
}

// 'service' can now be used to invoke operations.
```


## Container Authentication
The `ContainerAuthenticator` is intended to be used by application code
running inside a compute resource managed by the IBM Kubernetes Service (IKS)
Expand All @@ -312,7 +457,7 @@ within the compute resource's local file system.
The CR token is similar to an IAM apikey except that it is managed automatically by
the compute resource provider (IKS).
This allows the application developer to:
- avoid storing credentials in application code, configuraton files or a password vault
- avoid storing credentials in application code, configuration files or a password vault
- avoid managing or rotating credentials

The `ContainerAuthenticator` will retrieve the CR token from
Expand Down Expand Up @@ -362,7 +507,7 @@ are optional, but must be specified together.
- Scope: (optional) the scope to be associated with the IAM access token.
If not specified, then no scope will be associated with the access token.

- DisableSSLVerification: (optional) A flag that indicates whether verificaton of the server's SSL
- DisableSSLVerification: (optional) A flag that indicates whether verification of the server's SSL
certificate should be disabled or not. The default value is `false`.

- Headers: (optional) A set of key/value pairs that will be sent as HTTP headers in requests
Expand Down Expand Up @@ -436,7 +581,7 @@ The compute resource identity feature allows you to assign a trusted IAM profile
This, in turn, allows applications running within the compute resource to take on this identity when interacting with
IAM-secured IBM Cloud services.
This results in a simplified security model that allows the application developer to:
- avoid storing credentials in application code, configuraton files or a password vault
- avoid storing credentials in application code, configuration files or a password vault
- avoid managing or rotating credentials

The `VpcInstanceAuthenticator` will invoke the appropriate operations on the compute resource's locally-available
Expand Down Expand Up @@ -552,7 +697,7 @@ Exactly one of Password or APIKey should be specified.
- URL: (required) The URL representing the Cloud Pak for Data token service endpoint's base URL string.
This value should not include the `/v1/authorize` path portion.

- DisableSSLVerification: (optional) A flag that indicates whether verificaton of the server's SSL
- DisableSSLVerification: (optional) A flag that indicates whether verification of the server's SSL
certificate should be disabled or not. The default value is `false`.

- Headers: (optional) A set of key/value pairs that will be sent as HTTP headers in requests
Expand Down Expand Up @@ -640,7 +785,7 @@ form:
- URL: (required) The URL representing the MCSP token service endpoint's base URL string. Do not include the
operation path (e.g. `/siusermgr/api/1.0/apikeys/token`) as part of this property's value.

- DisableSSLVerification: (optional) A flag that indicates whether verificaton of the server's SSL
- DisableSSLVerification: (optional) A flag that indicates whether verification of the server's SSL
certificate should be disabled or not. The default value is `false`.

- Headers: (optional) A set of key/value pairs that will be sent as HTTP headers in requests
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ go get -u github.com/IBM/go-sdk-core/...
The go-sdk-core project supports the following types of authentication:
- Basic Authentication
- Bearer Token Authentication
- Identity and Access Management (IAM) Authentication
- Identity and Access Management (IAM) Authentication (grant type: apikey)
- Identity and Access Management (IAM) Authentication (grant type: assume)
- Container Authentication
- VPC Instance Authentication
- Cloud Pak for Data Authentication
Expand Down
2 changes: 2 additions & 0 deletions core/authenticator_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ func GetAuthenticatorFromEnvironment(credentialKey string) (authenticator Authen
authenticator, err = newBearerTokenAuthenticatorFromMap(properties)
} else if strings.EqualFold(authType, AUTHTYPE_IAM) {
authenticator, err = newIamAuthenticatorFromMap(properties)
} else if strings.EqualFold(authType, AUTHTYPE_IAM_ASSUME) {
authenticator, err = newIamAssumeAuthenticatorFromMap(properties)
} else if strings.EqualFold(authType, AUTHTYPE_CONTAINER) {
authenticator, err = newContainerAuthenticatorFromMap(properties)
} else if strings.EqualFold(authType, AUTHTYPE_VPC) {
Expand Down
32 changes: 28 additions & 4 deletions core/authenticator_factory_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build all || fast
//go:build all || slow || auth

package core

Expand Down Expand Up @@ -27,9 +27,9 @@ var (
authFactoryTestLogLevel LogLevel = LevelError
)

// Note: the following functions are used from the config_utils_test.go file:
// setTestEnvironment()
// setTestVCAP()
// Note: the following functions are used from other files:
// setTestEnvironment() (common_test.go)
// setTestVCAP() (config_utils_test.go)

func TestGetAuthenticatorFromEnvironment1(t *testing.T) {
GetLogger().SetLogLevel(authFactoryTestLogLevel)
Expand Down Expand Up @@ -136,6 +136,19 @@ func TestGetAuthenticatorFromEnvironment1(t *testing.T) {
assert.Equal(t, "my-api-key", mcspAuth.ApiKey)
assert.Equal(t, "https://mcsp.ibm.com", mcspAuth.URL)
assert.True(t, mcspAuth.DisableSSLVerification)

// Iam Assume Authenticator.
authenticator, err = GetAuthenticatorFromEnvironment("service11")
assert.Nil(t, err)
assert.NotNil(t, authenticator)
assert.Equal(t, AUTHTYPE_IAM_ASSUME, authenticator.AuthenticationType())
iamAssume, ok := authenticator.(*IamAssumeAuthenticator)
assert.True(t, ok)
assert.NotNil(t, iamAssume)
assert.Equal(t, "my-api-key", iamAssume.iamDelegate.ApiKey)
assert.Equal(t, "iam-profile-1", iamAssume.iamProfileID)
assert.Equal(t, "https://iamassume.ibm.com", iamAssume.url)
assert.True(t, iamAssume.disableSSLVerification)
}

func TestGetAuthenticatorFromEnvironment2(t *testing.T) {
Expand Down Expand Up @@ -236,6 +249,17 @@ func TestGetAuthenticatorFromEnvironment2(t *testing.T) {
assert.Equal(t, "my-api-key", mcspAuth.ApiKey)
assert.Equal(t, "https://mcsp.ibm.com", mcspAuth.URL)
assert.True(t, mcspAuth.DisableSSLVerification)

authenticator, err = GetAuthenticatorFromEnvironment("service15")
assert.Nil(t, err)
assert.NotNil(t, authenticator)
assert.Equal(t, AUTHTYPE_IAM_ASSUME, authenticator.AuthenticationType())
iamAssume, ok := authenticator.(*IamAssumeAuthenticator)
assert.True(t, ok)
assert.NotNil(t, iamAssume)
assert.Equal(t, "my-apikey", iamAssume.iamDelegate.ApiKey)
assert.Equal(t, "https://iam.assume.ibm.com", iamAssume.url)
assert.False(t, iamAssume.disableSSLVerification)
}

func TestGetAuthenticatorFromEnvironment3(t *testing.T) {
Expand Down
5 changes: 5 additions & 0 deletions core/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ var testEnvironment = map[string]string{
"SERVICE14_AUTH_URL": "https://mcsp.ibm.com",
"SERVICE14_APIKEY": "my-api-key",
"SERVICE14_AUTH_DISABLE_SSL": "true",
"SERVICE15_AUTH_URL": "https://iam.assume.ibm.com",
"SERVICE15_AUTH_TYPE": "IAMAssUME",
"SERVICE15_APIKEY": "my-apikey",
"SERVICE15_IAM_PROFILE_NAME": "profile-1",
"SERVICE15_IAM_ACCOUNT_ID": "account-1",
}

// setTestEnvironment sets the environment variables described in our map.
Expand Down
3 changes: 3 additions & 0 deletions core/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
AUTHTYPE_BEARER_TOKEN = "bearerToken"
AUTHTYPE_NOAUTH = "noAuth"
AUTHTYPE_IAM = "iam"
AUTHTYPE_IAM_ASSUME = "iamAssume"
AUTHTYPE_CP4D = "cp4d"
AUTHTYPE_CONTAINER = "container"
AUTHTYPE_VPC = "vpc"
Expand Down Expand Up @@ -52,6 +53,7 @@ const (
PROPNAME_IAM_PROFILE_CRN = "IAM_PROFILE_CRN"
PROPNAME_IAM_PROFILE_NAME = "IAM_PROFILE_NAME"
PROPNAME_IAM_PROFILE_ID = "IAM_PROFILE_ID"
PROPNAME_IAM_ACCOUNT_ID = "IAM_ACCOUNT_ID"

// SSL error
SSL_CERTIFICATION_ERROR = "x509: certificate"
Expand Down Expand Up @@ -83,6 +85,7 @@ const (
ERRORMSG_IAM_GETTOKEN_ERROR = "IAM 'get token' error, status code %d received from '%s': %s" // #nosec G101
ERRORMSG_UNABLE_RETRIEVE_IITOKEN = "unable to retrieve instance identity token value: %s" // #nosec G101
ERRORMSG_VPCMDS_OPERATION_ERROR = "VPC metadata service error, status code %d received from '%s': %s"
ERRORMSG_ACCOUNTID_PROP_ERROR = "IAMAccountID must be specified if and only if IAMProfileName is specified"

// The name of this module - matches the value in the go.mod file.
MODULE_NAME = "github.com/IBM/go-sdk-core/v5"
Expand Down
Loading

0 comments on commit df85f15

Please sign in to comment.