Skip to content

Commit

Permalink
spread/google: add support for service accounts
Browse files Browse the repository at this point in the history
Add support for attaching service accounts to the instances created in GCP.

Signed-off-by: Maciej Borzecki <[email protected]>
  • Loading branch information
bboozzoo committed Jun 24, 2024
1 parent ded9133 commit 07c7be1
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 7 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,26 @@ Service accounts are best as they can be further constrained and not be
associated with your overall authenticated access. Do not ship your own
credentials to remote systems.

A service account can be attached to a created instances using the following
configuration:

```
(...)
backends:
google:
key: $(HOST:echo $GOOGLE_JSON_FILENAME)
...
systems:
- system-with-service-account:
attach-service-account: true
...
```

Service account can only be attached to an instance if the authentication key is
of `service_acccount` type, and the IAM role associated with the account has the
necessary permissions.

Images are located by first attempting to match the provided value exactly
against the image name, and then some processing is done to verify if an
image with the individual tokens in its description exists. Images are
Expand Down
65 changes: 58 additions & 7 deletions spread/google.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
"golang.org/x/net/context"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/jwt"

"github.com/niemeyer/pretty"
"regexp"
Expand Down Expand Up @@ -44,6 +43,8 @@ type googleProvider struct {

client *http.Client

serviceAccount string

mu sync.Mutex

keyChecked bool
Expand Down Expand Up @@ -459,6 +460,20 @@ func (p *googleProvider) createMachine(ctx context.Context, system *System) (*go
},
}

if system.AttachServiceAccount {
if p.serviceAccount == "" {
return nil, &FatalError{fmt.Errorf("no service account to attach")}
}
// XXX the service account could be set from google key
// credentials, but the account used in the context of the
// request may not have the permissions to attach a service
// account to the instance
params["serviceAccounts"] = []googleParams{{
"email": p.serviceAccount,
"scopes": []string{"https://www.googleapis.com/auth/cloud-platform"},
}}
}

if system.SecureBoot {
params["shieldedInstanceConfig"] = googleParams{
"enableSecureBoot": true,
Expand Down Expand Up @@ -755,6 +770,28 @@ func (p *googleProvider) waitOperation(ctx context.Context, s *googleServer, ver
panic("unreachable")
}

func serviceAccountFromKey(raw []byte) (string, error) {
const serviceAccountKey = "service_account"

// taken from golang.org/x/oauth/google
var credentials struct {
Type string `json:"type"`
ClientEmail string `json:"client_email"`
}

if err := json.Unmarshal(raw, &credentials); err != nil {
return "", err
}

if credentials.Type != serviceAccountKey {
// email is only relevant if dealing with service_account
// credentials type
return "", nil
}

return credentials.ClientEmail, nil
}

func (p *googleProvider) checkKey() error {
p.mu.Lock()
defer p.mu.Unlock()
Expand All @@ -771,15 +808,29 @@ func (p *googleProvider) checkKey() error {

if err == nil && p.client == nil {
ctx := context.Background()
if strings.HasPrefix(p.backend.Key, "{") {
var cfg *jwt.Config
cfg, err = google.JWTConfigFromJSON([]byte(p.backend.Key), googleScope)
var creds *google.Credentials
if p.backend.Key != "" {
var raw []byte
if strings.HasPrefix(p.backend.Key, "{") {
// already a raw JSON blob
raw = []byte(p.backend.Key)
} else {
raw, err = ioutil.ReadFile(p.backend.Key)
}

if err == nil {
p.client = oauth2.NewClient(ctx, cfg.TokenSource(ctx))
creds, err = google.CredentialsFromJSON(ctx, raw, googleScope)
}

// identify service account if possible
p.serviceAccount, err = serviceAccountFromKey(raw)
} else {
os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", p.backend.Key)
p.client, err = google.DefaultClient(context.Background(), googleScope)
// none provided, let the google library find whatever
// is appropriate
creds, err = google.FindDefaultCredentials(ctx, googleScope)
}
if err == nil {
p.client = oauth2.NewClient(ctx, creds.TokenSource)
}
}
if err == nil {
Expand Down
3 changes: 3 additions & 0 deletions spread/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ type System struct {

Priority OptionalInt
Manual bool

// Only for Google
AttachServiceAccount bool `yaml:"attach-service-account"`
}

func (system *System) String() string { return system.Backend + ":" + system.Name }
Expand Down

0 comments on commit 07c7be1

Please sign in to comment.