Skip to content

Commit

Permalink
feat: add exp field to JWT if expires_in is set (#6429) (#4677)
Browse files Browse the repository at this point in the history
Signed-off-by: Modular Magician <[email protected]>

Signed-off-by: Modular Magician <[email protected]>
  • Loading branch information
modular-magician authored Sep 12, 2022
1 parent d70bb52 commit a364020
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .changelog/6429.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
google_service_account_jwt: added `expires_in` attribute for generating `exp` claim.
```
33 changes: 32 additions & 1 deletion google-beta/data_source_google_service_account_jwt.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package google

import (
"encoding/json"
"fmt"
"strings"
"time"

iamcredentials "google.golang.org/api/iamcredentials/v1"

Expand All @@ -18,6 +20,11 @@ func dataSourceGoogleServiceAccountJwt() *schema.Resource {
Required: true,
Description: `A JSON-encoded JWT claims set that will be included in the signed JWT.`,
},
"expires_in": {
Type: schema.TypeInt,
Optional: true,
Description: "Number of seconds until the JWT expires. If set and non-zero an `exp` claim will be added to the payload derived from the current timestamp plus expires_in seconds.",
},
"target_service_account": {
Type: schema.TypeString,
Required: true,
Expand All @@ -40,6 +47,10 @@ func dataSourceGoogleServiceAccountJwt() *schema.Resource {
}
}

var (
dataSourceGoogleServiceAccountJwtNow = time.Now
)

func dataSourceGoogleServiceAccountJwtRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)

Expand All @@ -49,10 +60,30 @@ func dataSourceGoogleServiceAccountJwtRead(d *schema.ResourceData, meta interfac
return err
}

payload := d.Get("payload").(string)

if expiresIn := d.Get("expires_in").(int); expiresIn != 0 {
var decoded map[string]interface{}

if err := json.Unmarshal([]byte(payload), &decoded); err != nil {
return fmt.Errorf("error decoding `payload` while adding `exp` field: %w", err)
}

decoded["exp"] = dataSourceGoogleServiceAccountJwtNow().Add(time.Duration(expiresIn) * time.Second).Unix()

payloadBytesWithExp, err := json.Marshal(decoded)

if err != nil {
return fmt.Errorf("error re-encoding `payload` while adding `exp` field: %w", err)
}

payload = string(payloadBytesWithExp)
}

name := fmt.Sprintf("projects/-/serviceAccounts/%s", d.Get("target_service_account").(string))

jwtRequest := &iamcredentials.SignJwtRequest{
Payload: d.Get("payload").(string),
Payload: payload,
Delegates: convertStringSet(d.Get("delegates").(*schema.Set)),
}

Expand Down
31 changes: 30 additions & 1 deletion google-beta/data_source_google_service_account_jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"errors"
"strings"
"testing"
"time"

"fmt"

Expand All @@ -18,6 +19,7 @@ const (
jwtTestSubject = "custom-subject"
jwtTestFoo = "bar"
jwtTestComplexFooNested = "baz"
jwtTestExpiresIn = 60
)

type jwtTestPayload struct {
Expand All @@ -28,6 +30,8 @@ type jwtTestPayload struct {
ComplexFoo struct {
Nested string `json:"nested"`
} `json:"complexFoo"`

Expiration int64 `json:"exp"`
}

func testAccCheckServiceAccountJwtValue(name, audience string) resource.TestCheckFunc {
Expand Down Expand Up @@ -78,6 +82,12 @@ func testAccCheckServiceAccountJwtValue(name, audience string) resource.TestChec
return fmt.Errorf("invalid 'foo', expected '%s', got '%s'", jwtTestComplexFooNested, payload.ComplexFoo.Nested)
}

expectedExpiration := dataSourceGoogleServiceAccountJwtNow().Add(jwtTestExpiresIn * time.Second).Unix()

if payload.Expiration != expectedExpiration {
return fmt.Errorf("invalid 'exp', expected '%d', got '%d'", expectedExpiration, payload.Expiration)
}

return nil
}
}
Expand All @@ -89,6 +99,13 @@ func TestAccDataSourceGoogleServiceAccountJwt(t *testing.T) {
serviceAccount := getTestServiceAccountFromEnv(t)
targetServiceAccountEmail := BootstrapServiceAccount(t, getTestProjectFromEnv(), serviceAccount)

staticTime := time.Now()

// Override the current time with one that is set to a static value, to compare against later.
dataSourceGoogleServiceAccountJwtNow = func() time.Time {
return staticTime
}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Expand All @@ -99,6 +116,16 @@ func TestAccDataSourceGoogleServiceAccountJwt(t *testing.T) {
testAccCheckServiceAccountJwtValue(resourceName, targetAudience),
),
},
{
PreConfig: func() {
// Bump the hardcoded time to ensure terraform responds well to the JWT expiration changing.
staticTime = time.Now().Add(10 * time.Second)
},
Config: testAccCheckGoogleServiceAccountJwt(targetServiceAccountEmail),
Check: resource.ComposeTestCheckFunc(
testAccCheckServiceAccountJwtValue(resourceName, targetAudience),
),
},
},
})
}
Expand All @@ -115,6 +142,8 @@ data "google_service_account_jwt" "default" {
nested: "%s"
}
})
expires_in = %d
}
`, targetServiceAccount, jwtTestSubject, jwtTestFoo, jwtTestComplexFooNested)
`, targetServiceAccount, jwtTestSubject, jwtTestFoo, jwtTestComplexFooNested, jwtTestExpiresIn)
}
3 changes: 3 additions & 0 deletions website/docs/d/service_account_jwt.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ data "google_service_account_jwt" "foo" {
foo: "bar",
sub: "subject",
})
expires_in = 60
}
output "jwt" {
Expand All @@ -36,6 +38,7 @@ The following arguments are supported:

* `target_service_account` (Required) - The email of the service account that will sign the JWT.
* `payload` (Required) - The JSON-encoded JWT claims set to include in the self-signed JWT.
* `expires_in` (Optional) - Number of seconds until the JWT expires. If set and non-zero an `exp` claim will be added to the payload derived from the current timestamp plus expires_in seconds.
* `delegates` (Optional) - Delegate chain of approvals needed to perform full impersonation. Specify the fully qualified service account name.

## Attributes Reference
Expand Down

0 comments on commit a364020

Please sign in to comment.