parent
4cef086c00
commit
c431538ff2
5 changed files with 60 additions and 9 deletions
|
@ -43,3 +43,11 @@ func (d *Duration) UnmarshalJSON(data []byte) (err error) {
|
||||||
d.Duration = _d
|
d.Duration = _d
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Value returns 0 if the duration is null, the inner duration otherwise.
|
||||||
|
func (d *Duration) Value() time.Duration {
|
||||||
|
if d == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return d.Duration
|
||||||
|
}
|
||||||
|
|
|
@ -59,3 +59,24 @@ func TestDuration_MarshalJSON(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDuration_Value(t *testing.T) {
|
||||||
|
var dur *Duration
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
duration *Duration
|
||||||
|
want time.Duration
|
||||||
|
}{
|
||||||
|
{"ok", &Duration{Duration: 1 * time.Minute}, 1 * time.Minute},
|
||||||
|
{"ok new", new(Duration), 0},
|
||||||
|
{"ok nil", nil, 0},
|
||||||
|
{"ok nil var", dur, 0},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := tt.duration.Value(); got != tt.want {
|
||||||
|
t.Errorf("Duration.Value() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@ type GCP struct {
|
||||||
ProjectIDs []string `json:"projectIDs"`
|
ProjectIDs []string `json:"projectIDs"`
|
||||||
DisableCustomSANs bool `json:"disableCustomSANs"`
|
DisableCustomSANs bool `json:"disableCustomSANs"`
|
||||||
DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"`
|
DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"`
|
||||||
|
InstanceAge Duration `json:"instanceAge,omitempty"`
|
||||||
Claims *Claims `json:"claims,omitempty"`
|
Claims *Claims `json:"claims,omitempty"`
|
||||||
claimer *Claimer
|
claimer *Claimer
|
||||||
config *gcpConfig
|
config *gcpConfig
|
||||||
|
@ -177,6 +178,8 @@ func (p *GCP) Init(config Config) error {
|
||||||
return errors.New("provisioner type cannot be empty")
|
return errors.New("provisioner type cannot be empty")
|
||||||
case p.Name == "":
|
case p.Name == "":
|
||||||
return errors.New("provisioner name cannot be empty")
|
return errors.New("provisioner name cannot be empty")
|
||||||
|
case p.InstanceAge.Value() < 0:
|
||||||
|
return errors.New("provisioner instanceAge cannot be negative")
|
||||||
}
|
}
|
||||||
// Initialize config
|
// Initialize config
|
||||||
p.assertConfig()
|
p.assertConfig()
|
||||||
|
@ -271,9 +274,10 @@ func (p *GCP) authorizeToken(token string) (*gcpPayload, error) {
|
||||||
|
|
||||||
// According to "rfc7519 JSON Web Token" acceptable skew should be no
|
// According to "rfc7519 JSON Web Token" acceptable skew should be no
|
||||||
// more than a few minutes.
|
// more than a few minutes.
|
||||||
|
now := time.Now().UTC()
|
||||||
if err = claims.ValidateWithLeeway(jose.Expected{
|
if err = claims.ValidateWithLeeway(jose.Expected{
|
||||||
Issuer: "https://accounts.google.com",
|
Issuer: "https://accounts.google.com",
|
||||||
Time: time.Now().UTC(),
|
Time: now,
|
||||||
}, time.Minute); err != nil {
|
}, time.Minute); err != nil {
|
||||||
return nil, errors.Wrapf(err, "invalid token")
|
return nil, errors.Wrapf(err, "invalid token")
|
||||||
}
|
}
|
||||||
|
@ -311,6 +315,13 @@ func (p *GCP) authorizeToken(token string) (*gcpPayload, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validate instance age
|
||||||
|
if d := p.InstanceAge.Value(); d > 0 {
|
||||||
|
if now.Sub(claims.Google.ComputeEngine.InstanceCreationTimestamp.Time()) > d {
|
||||||
|
return nil, errors.New("token google.compute_engine.instance_creation_timestamp is too old")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case claims.Google.ComputeEngine.InstanceID == "":
|
case claims.Google.ComputeEngine.InstanceID == "":
|
||||||
return nil, errors.New("token google.compute_engine.instance_id cannot be empty")
|
return nil, errors.New("token google.compute_engine.instance_id cannot be empty")
|
||||||
|
|
|
@ -159,11 +159,12 @@ func TestGCP_Init(t *testing.T) {
|
||||||
badClaims := &Claims{
|
badClaims := &Claims{
|
||||||
DefaultTLSDur: &Duration{0},
|
DefaultTLSDur: &Duration{0},
|
||||||
}
|
}
|
||||||
|
zero := Duration{Duration: 0}
|
||||||
type fields struct {
|
type fields struct {
|
||||||
Type string
|
Type string
|
||||||
Name string
|
Name string
|
||||||
ServiceAccounts []string
|
ServiceAccounts []string
|
||||||
|
InstanceAge Duration
|
||||||
Claims *Claims
|
Claims *Claims
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
|
@ -176,12 +177,14 @@ func TestGCP_Init(t *testing.T) {
|
||||||
args args
|
args args
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"ok", fields{"GCP", "name", nil, nil}, args{config, srv.URL}, false},
|
{"ok", fields{"GCP", "name", nil, zero, nil}, args{config, srv.URL}, false},
|
||||||
{"ok", fields{"GCP", "name", []string{"service-account"}, nil}, args{config, srv.URL}, false},
|
{"ok", fields{"GCP", "name", []string{"service-account"}, zero, nil}, args{config, srv.URL}, false},
|
||||||
{"bad type", fields{"", "name", nil, nil}, args{config, srv.URL}, true},
|
{"ok", fields{"GCP", "name", []string{"service-account"}, Duration{Duration: 1 * time.Minute}, nil}, args{config, srv.URL}, false},
|
||||||
{"bad name", fields{"GCP", "", nil, nil}, args{config, srv.URL}, true},
|
{"bad type", fields{"", "name", nil, zero, nil}, args{config, srv.URL}, true},
|
||||||
{"bad claims", fields{"GCP", "name", nil, badClaims}, args{config, srv.URL}, true},
|
{"bad name", fields{"GCP", "", nil, zero, nil}, args{config, srv.URL}, true},
|
||||||
{"bad certs", fields{"GCP", "name", nil, nil}, args{config, srv.URL + "/error"}, true},
|
{"bad duration", fields{"GCP", "name", nil, Duration{Duration: -1 * time.Minute}, nil}, args{config, srv.URL}, true},
|
||||||
|
{"bad claims", fields{"GCP", "name", nil, zero, badClaims}, args{config, srv.URL}, true},
|
||||||
|
{"bad certs", fields{"GCP", "name", nil, zero, nil}, args{config, srv.URL + "/error"}, true},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -189,6 +192,7 @@ func TestGCP_Init(t *testing.T) {
|
||||||
Type: tt.fields.Type,
|
Type: tt.fields.Type,
|
||||||
Name: tt.fields.Name,
|
Name: tt.fields.Name,
|
||||||
ServiceAccounts: tt.fields.ServiceAccounts,
|
ServiceAccounts: tt.fields.ServiceAccounts,
|
||||||
|
InstanceAge: tt.fields.InstanceAge,
|
||||||
Claims: tt.fields.Claims,
|
Claims: tt.fields.Claims,
|
||||||
config: &gcpConfig{
|
config: &gcpConfig{
|
||||||
CertsURL: tt.args.certsURL,
|
CertsURL: tt.args.certsURL,
|
||||||
|
@ -214,6 +218,7 @@ func TestGCP_AuthorizeSign(t *testing.T) {
|
||||||
assert.FatalError(t, err)
|
assert.FatalError(t, err)
|
||||||
p3.ProjectIDs = []string{"other-project-id"}
|
p3.ProjectIDs = []string{"other-project-id"}
|
||||||
p3.ServiceAccounts = []string{"foo@developer.gserviceaccount.com"}
|
p3.ServiceAccounts = []string{"foo@developer.gserviceaccount.com"}
|
||||||
|
p3.InstanceAge = Duration{1 * time.Minute}
|
||||||
|
|
||||||
aKey, err := generateJSONWebKey()
|
aKey, err := generateJSONWebKey()
|
||||||
assert.FatalError(t, err)
|
assert.FatalError(t, err)
|
||||||
|
@ -269,6 +274,11 @@ func TestGCP_AuthorizeSign(t *testing.T) {
|
||||||
"instance-id", "instance-name", "project-id", "zone",
|
"instance-id", "instance-name", "project-id", "zone",
|
||||||
time.Now(), &p3.keyStore.keySet.Keys[0])
|
time.Now(), &p3.keyStore.keySet.Keys[0])
|
||||||
assert.FatalError(t, err)
|
assert.FatalError(t, err)
|
||||||
|
failInvalidInstanceAge, err := generateGCPToken(p3.ServiceAccounts[0],
|
||||||
|
"https://accounts.google.com", p3.GetID(),
|
||||||
|
"instance-id", "instance-name", "other-project-id", "zone",
|
||||||
|
time.Now().Add(-1*time.Minute), &p3.keyStore.keySet.Keys[0])
|
||||||
|
assert.FatalError(t, err)
|
||||||
failInstanceID, err := generateGCPToken(p1.ServiceAccounts[0],
|
failInstanceID, err := generateGCPToken(p1.ServiceAccounts[0],
|
||||||
"https://accounts.google.com", p1.GetID(),
|
"https://accounts.google.com", p1.GetID(),
|
||||||
"", "instance-name", "project-id", "zone",
|
"", "instance-name", "project-id", "zone",
|
||||||
|
@ -311,6 +321,7 @@ func TestGCP_AuthorizeSign(t *testing.T) {
|
||||||
{"fail nbf", p1, args{failNbf}, 0, true},
|
{"fail nbf", p1, args{failNbf}, 0, true},
|
||||||
{"fail service account", p1, args{failServiceAccount}, 0, true},
|
{"fail service account", p1, args{failServiceAccount}, 0, true},
|
||||||
{"fail invalid project id", p3, args{failInvalidProjectID}, 0, true},
|
{"fail invalid project id", p3, args{failInvalidProjectID}, 0, true},
|
||||||
|
{"fail invalid instance age", p3, args{failInvalidInstanceAge}, 0, true},
|
||||||
{"fail instance id", p1, args{failInstanceID}, 0, true},
|
{"fail instance id", p1, args{failInstanceID}, 0, true},
|
||||||
{"fail instance name", p1, args{failInstanceName}, 0, true},
|
{"fail instance name", p1, args{failInstanceName}, 0, true},
|
||||||
{"fail project id", p1, args{failProjectID}, 0, true},
|
{"fail project id", p1, args{failProjectID}, 0, true},
|
||||||
|
|
|
@ -513,7 +513,7 @@ func generateGCPToken(sub, iss, aud, instanceID, instanceName, projectID, zone s
|
||||||
ComputeEngine: gcpComputeEnginePayload{
|
ComputeEngine: gcpComputeEnginePayload{
|
||||||
InstanceID: instanceID,
|
InstanceID: instanceID,
|
||||||
InstanceName: instanceName,
|
InstanceName: instanceName,
|
||||||
InstanceCreationTimestamp: jose.NewNumericDate(iat.Add(-24 * time.Hour)),
|
InstanceCreationTimestamp: jose.NewNumericDate(iat),
|
||||||
ProjectID: projectID,
|
ProjectID: projectID,
|
||||||
ProjectNumber: 1234567890,
|
ProjectNumber: 1234567890,
|
||||||
Zone: zone,
|
Zone: zone,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue