github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/service_account.go (about) 1 /* 2 * Copyright 2023 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. 3 */ 4 package govcd 5 6 import ( 7 "fmt" 8 "net/url" 9 "strings" 10 11 "github.com/vmware/go-vcloud-director/v2/types/v56" 12 ) 13 14 type ServiceAccount struct { 15 ServiceAccount *types.ServiceAccount 16 authParams *types.ServiceAccountAuthParams 17 org *Org 18 } 19 20 // GetServiceAccountById gets a Service Account by its ID 21 func (org *Org) GetServiceAccountById(serviceAccountId string) (*ServiceAccount, error) { 22 client := org.client 23 24 endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointServiceAccounts 25 apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint) 26 if err != nil { 27 return nil, err 28 } 29 30 urlRef, err := client.OpenApiBuildEndpoint(endpoint, serviceAccountId) 31 if err != nil { 32 return nil, err 33 } 34 35 newServiceAccount := &ServiceAccount{ 36 ServiceAccount: &types.ServiceAccount{}, 37 org: org, 38 } 39 40 err = client.OpenApiGetItem(apiVersion, urlRef, nil, newServiceAccount.ServiceAccount, nil) 41 if err != nil { 42 return nil, err 43 } 44 45 return newServiceAccount, nil 46 } 47 48 // GetAllServiceAccounts gets all service accounts with the specified query parameters 49 func (org *Org) GetAllServiceAccounts(queryParams url.Values) ([]*ServiceAccount, error) { 50 client := org.client 51 52 endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointServiceAccounts 53 apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint) 54 if err != nil { 55 return nil, err 56 } 57 58 urlRef, err := client.OpenApiBuildEndpoint(endpoint) 59 if err != nil { 60 return nil, err 61 } 62 63 tenantContext, err := org.getTenantContext() 64 if err != nil { 65 return nil, err 66 } 67 68 // VCD has a pageSize limit on this specific endpoint 69 queryParams.Add("pageSize", "32") 70 typeResponses := []*types.ServiceAccount{{}} 71 err = client.OpenApiGetAllItems(apiVersion, urlRef, queryParams, &typeResponses, getTenantContextHeader(tenantContext)) 72 73 if err != nil { 74 return nil, fmt.Errorf("failed to get service accounts: %s", err) 75 } 76 77 results := make([]*ServiceAccount, len(typeResponses)) 78 for sliceIndex := range typeResponses { 79 results[sliceIndex] = &ServiceAccount{ 80 ServiceAccount: typeResponses[sliceIndex], 81 org: org, 82 } 83 } 84 85 return results, nil 86 } 87 88 // GetServiceAccountByName gets a service account by its name 89 func (org *Org) GetServiceAccountByName(name string) (*ServiceAccount, error) { 90 queryParams := url.Values{} 91 queryParams.Add("filter", fmt.Sprintf("name==%s", name)) 92 93 serviceAccounts, err := org.GetAllServiceAccounts(queryParams) 94 if err != nil { 95 return nil, fmt.Errorf("error getting service account by name: %s", err) 96 } 97 98 serviceAccount, err := oneOrError("name", name, serviceAccounts) 99 if err != nil { 100 return nil, err 101 } 102 103 return serviceAccount, nil 104 } 105 106 // CreateServiceAccount creates a Service Account and sets it in `Created` status 107 func (vcdClient *VCDClient) CreateServiceAccount(orgName, name, scope, softwareId, softwareVersion, clientUri string) (*ServiceAccount, error) { 108 saParams := &types.ApiTokenParams{ 109 ClientName: name, 110 Scope: scope, 111 SoftwareID: softwareId, 112 SoftwareVersion: softwareVersion, 113 ClientURI: clientUri, 114 } 115 116 newSaParams, err := vcdClient.RegisterToken(orgName, saParams) 117 if err != nil { 118 return nil, fmt.Errorf("failed to register Service account: %s", err) 119 } 120 121 org, err := vcdClient.GetOrgByName(orgName) 122 if err != nil { 123 return nil, fmt.Errorf("failed to get Org by name: %s", err) 124 } 125 126 serviceAccountID := "urn:vcloud:serviceAccount:" + newSaParams.ClientID 127 serviceAccount, err := org.GetServiceAccountById(serviceAccountID) 128 if err != nil { 129 return nil, fmt.Errorf("failed to get Service account by ID: %s", err) 130 } 131 132 return serviceAccount, nil 133 } 134 135 // Update updates the modifiable fields of a Service Account 136 func (sa *ServiceAccount) Update(saConfig *types.ServiceAccount) (*ServiceAccount, error) { 137 client := sa.org.client 138 endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointServiceAccounts 139 apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint) 140 if err != nil { 141 return nil, err 142 } 143 144 saConfig.ID = sa.ServiceAccount.ID 145 saConfig.Name = sa.ServiceAccount.Name 146 saConfig.Status = sa.ServiceAccount.Status 147 urlRef, err := client.OpenApiBuildEndpoint(endpoint, saConfig.ID) 148 if err != nil { 149 return nil, err 150 } 151 152 returnServiceAccount := &ServiceAccount{ 153 ServiceAccount: &types.ServiceAccount{}, 154 org: sa.org, 155 } 156 157 err = client.OpenApiPutItem(apiVersion, urlRef, nil, saConfig, returnServiceAccount.ServiceAccount, nil) 158 if err != nil { 159 return nil, fmt.Errorf("error updating Service Account: %s", err) 160 } 161 162 return returnServiceAccount, nil 163 } 164 165 // Authorize authorizes a service account and returns a DeviceID and UserCode which will be used while granting 166 // the request, and sets the Service Account in `Requested` status 167 func (sa *ServiceAccount) Authorize() error { 168 client := sa.org.client 169 170 uuid := extractUuid(sa.ServiceAccount.ID) 171 data := map[string]string{ 172 "client_id": uuid, 173 } 174 175 userDef := "tenant/" + sa.org.orgName() 176 if strings.EqualFold(sa.org.orgName(), "system") { 177 userDef = "provider" 178 } 179 180 endpoint := fmt.Sprintf("%s://%s/oauth/%s/device_authorization", client.VCDHREF.Scheme, client.VCDHREF.Host, userDef) 181 urlRef, err := url.ParseRequestURI(endpoint) 182 if err != nil { 183 return fmt.Errorf("error getting request url from %s: %s", urlRef.String(), err) 184 } 185 186 // Not an OpenAPI endpoint so hardcoding the Service Account minimal version 187 err = client.OpenApiPostUrlEncoded("37.0", urlRef, nil, data, &sa.authParams, nil) 188 if err != nil { 189 return fmt.Errorf("error authorizing service account: %s", err) 190 } 191 192 return nil 193 } 194 195 // Grant Grants access to the Service Account and sets it in `Granted` status 196 func (sa *ServiceAccount) Grant() error { 197 if sa.authParams == nil { 198 return fmt.Errorf("error: userCode is unset, service account needs to be authorized") 199 } 200 201 client := sa.org.client 202 // This is the only place where this field is used, so a local struct is created 203 type serviceAccountGrant struct { 204 UserCode string `json:"userCode"` 205 } 206 207 userCode := &serviceAccountGrant{ 208 UserCode: sa.authParams.UserCode, 209 } 210 211 endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointServiceAccountGrant 212 apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint) 213 if err != nil { 214 return err 215 } 216 217 urlRef, err := client.OpenApiBuildEndpoint(endpoint) 218 if err != nil { 219 return fmt.Errorf("error granting service account: %s", err) 220 } 221 222 tenantContext, err := sa.org.getTenantContext() 223 if err != nil { 224 return fmt.Errorf("error granting service account: %s", err) 225 } 226 227 err = client.OpenApiPostItem(apiVersion, urlRef, nil, userCode, nil, getTenantContextHeader(tenantContext)) 228 if err != nil { 229 return fmt.Errorf("error granting service account: %s", err) 230 } 231 232 return nil 233 } 234 235 // GetInitialApiToken gets the initial API token for the Service Account and sets it in `Active` status 236 func (sa *ServiceAccount) GetInitialApiToken() (*types.ApiTokenRefresh, error) { 237 if sa.authParams == nil { 238 return nil, fmt.Errorf("error: service account must be authorized and granted") 239 } 240 client := sa.org.client 241 uuid := extractUuid(sa.ServiceAccount.ID) 242 data := map[string]string{ 243 "grant_type": "urn:ietf:params:oauth:grant-type:device_code", 244 "client_id": uuid, 245 "device_code": sa.authParams.DeviceCode, 246 } 247 token, err := client.getAccessToken(sa.ServiceAccount.Org.Name, "CreateServiceAccount", data) 248 if err != nil { 249 return nil, fmt.Errorf("error getting initial api token: %s", err) 250 } 251 return token, nil 252 } 253 254 // Refresh updates the Service Account object 255 func (sa *ServiceAccount) Refresh() error { 256 if sa.ServiceAccount == nil || sa.org.client == nil || sa.ServiceAccount.ID == "" { 257 return fmt.Errorf("cannot refresh Service Account without ID") 258 } 259 260 updatedServiceAccount, err := sa.org.GetServiceAccountById(sa.ServiceAccount.ID) 261 if err != nil { 262 return err 263 } 264 sa.ServiceAccount = updatedServiceAccount.ServiceAccount 265 266 return nil 267 } 268 269 // Revoke revokes the service account and its' API token and puts it back in 'Created' stage 270 func (sa *ServiceAccount) Revoke() error { 271 client := sa.org.client 272 273 endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointServiceAccounts 274 apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint) 275 if err != nil { 276 return err 277 } 278 279 urlRef, err := client.OpenApiBuildEndpoint(endpoint, sa.ServiceAccount.ID, "/revoke") 280 if err != nil { 281 return err 282 } 283 284 err = client.OpenApiPostItem(apiVersion, urlRef, nil, nil, nil, nil) 285 if err != nil { 286 return err 287 } 288 289 return nil 290 } 291 292 // Delete deletes a Service Account 293 func (sa *ServiceAccount) Delete() error { 294 client := sa.org.client 295 296 endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointServiceAccounts 297 apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint) 298 if err != nil { 299 return err 300 } 301 302 urlRef, err := client.OpenApiBuildEndpoint(endpoint, sa.ServiceAccount.ID) 303 if err != nil { 304 return err 305 } 306 307 err = client.OpenApiDeleteItem(apiVersion, urlRef, nil, nil) 308 if err != nil { 309 return err 310 } 311 312 return nil 313 } 314 315 // SetServiceAccountApiToken gets the refresh token from a provided file, fetches 316 // the bearer token and updates the provided file with the new refresh token for 317 // next usage as service account API tokens are one-time use 318 func (vcdClient *VCDClient) SetServiceAccountApiToken(org, apiTokenFile string) error { 319 if vcdClient.Client.APIVCDMaxVersionIs("< 37.0") { 320 version, err := vcdClient.Client.GetVcdFullVersion() 321 if err == nil { 322 return fmt.Errorf("minimum version for Service Account authentication is 10.4 - Version detected: %s", version.Version) 323 } 324 // If we can't get the VCD version, we return API version info 325 return fmt.Errorf("minimum API version for Service Account authentication is 37.0 - Version detected: %s", vcdClient.Client.APIVersion) 326 } 327 328 apiToken, err := vcdClient.SetApiTokenFromFile(org, apiTokenFile) 329 if err != nil { 330 return err 331 } 332 333 err = SaveServiceAccountToFile(apiTokenFile, vcdClient.Client.UserAgent, apiToken) 334 if err != nil { 335 return fmt.Errorf("failed to save service account token to %s: %s", apiTokenFile, err) 336 } 337 return nil 338 } 339 340 // SaveServiceAccountToFile saves the API token of the Service Account to a file 341 func SaveServiceAccountToFile(filename, useragent string, saToken *types.ApiTokenRefresh) error { 342 return saveTokenToFile(filename, "Service Account", useragent, saToken) 343 }