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 ability to authorize using a Service Account #562

Merged
merged 17 commits into from
Apr 19, 2023
2 changes: 2 additions & 0 deletions .changes/v2.20.0/562-improvements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* Add `SetServiceAccountApiToken` method of `*VCDClient` that allows
authenticating using a service account token file and handles the refresh token rotation [GH-562]
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func main() {

## Authentication

You can authenticate to the vCD in four ways:
You can authenticate to the vCD in five ways:

* With a System Administration user and password (`administrator@system`)
* With an Organization user and password (`tenant-admin@org-name`)
Expand All @@ -133,6 +133,11 @@ For the above two methods, you use:
The file `scripts/get_token.sh` provides a handy method of extracting the token
(`x-vcloud-authorization` value) for future use.

* With a service account token (the file needs to have `r+w` rights)
```go
err := vcdClient.SetServiceAccountApiToken(Org, "tokenfile.json")
```

* SAML user and password (works with ADFS as IdP using WS-TRUST endpoint
"/adfs/services/trust/13/usernamemixed"). One must pass `govcd.WithSamlAdfs(true,customAdfsRptId)`
and username must be formatted so that ADFS understands it ('user@contoso.com' or
Expand Down
47 changes: 47 additions & 0 deletions govcd/api_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"

"github.com/vmware/go-vcloud-director/v2/types/v56"
Expand All @@ -31,6 +33,51 @@ func (vcdClient *VCDClient) SetApiToken(org, apiToken string) (*types.ApiTokenRe
return tokenRefresh, nil
}

// SetServiceAccountApiToken reads the current Service Account API token,
// sets the client's bearer token and fetches a new API token for next
// authentication request using SetApiToken and overwrites the old file.
func (vcdClient *VCDClient) SetServiceAccountApiToken(org, apiTokenFile string) error {
if vcdClient.Client.APIVCDMaxVersionIs("< 37.0") {
version, err := vcdClient.Client.GetVcdFullVersion()
if err == nil {
return fmt.Errorf("minimum version for Service Account authentication is 10.4 - Version detected: %s", version.Version)
}
// If we can't get the VCD version, we return API version info
return fmt.Errorf("minimum API version for Service Account authentication is 37.0 - Version detected: %s", vcdClient.Client.APIVersion)
}

apiTokenFile = filepath.Clean(apiTokenFile)
data, err := os.ReadFile(apiTokenFile)
if err != nil {
return fmt.Errorf("failed to read tokenfile: %s", err)
}

saApiToken := &types.ApiTokenRefresh{}
err = json.Unmarshal(data, &saApiToken)
if err != nil {
return fmt.Errorf("failed to unmarshal tokenfile, check if your JSON file is valid: %s", err)
}

saApiToken, err = vcdClient.SetApiToken(org, saApiToken.RefreshToken)
if err != nil {
return err
}

data, err = json.Marshal(&types.ApiTokenRefresh{
RefreshToken: saApiToken.RefreshToken,
})
if err != nil {
return err
}

err = os.WriteFile(apiTokenFile, data, 0600)
if err != nil {
return err
}

return nil
}

// GetBearerTokenFromApiToken uses an API token to retrieve a bearer token
// using the refresh token operation.
func (vcdClient *VCDClient) GetBearerTokenFromApiToken(org, token string) (*types.ApiTokenRefresh, error) {
Expand Down
8 changes: 4 additions & 4 deletions types/v56/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3257,10 +3257,10 @@ type UpdateVdcStorageProfiles struct {

// ApiTokenRefresh contains the access token resulting from a refresh_token operation
type ApiTokenRefresh struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
RefreshToken interface{} `json:"refresh_token"`
AccessToken string `json:"access_token,omitempty"`
TokenType string `json:"token_type,omitempty"`
ExpiresIn int `json:"expires_in,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
}

/**/
Expand Down