Add support for instance age check in AWS.

Fixes smallstep/step#164
This commit is contained in:
Mariano Cano 2019-06-04 16:31:33 -07:00
parent c431538ff2
commit 536ec36b9e
3 changed files with 29 additions and 6 deletions

View file

@ -121,6 +121,7 @@ type AWS struct {
Accounts []string `json:"accounts"` Accounts []string `json:"accounts"`
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 *awsConfig config *awsConfig
@ -236,6 +237,8 @@ func (p *AWS) Init(config Config) (err 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")
} }
// Update claims with global ones // Update claims with global ones
if p.claimer, err = NewClaimer(p.Claims, config.Claims); err != nil { if p.claimer, err = NewClaimer(p.Claims, config.Claims); err != nil {
@ -370,11 +373,12 @@ func (p *AWS) authorizeToken(token string) (*awsPayload, 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 = payload.ValidateWithLeeway(jose.Expected{ if err = payload.ValidateWithLeeway(jose.Expected{
Issuer: awsIssuer, Issuer: awsIssuer,
Subject: doc.InstanceID, Subject: doc.InstanceID,
Audience: []string{p.GetID()}, Audience: []string{p.GetID()},
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")
} }
@ -392,6 +396,14 @@ func (p *AWS) authorizeToken(token string) (*awsPayload, error) {
return nil, errors.New("invalid identity document: accountId is not valid") return nil, errors.New("invalid identity document: accountId is not valid")
} }
} }
// validate instance age
if d := p.InstanceAge.Value(); d > 0 {
if now.Sub(doc.PendingTime) > d {
return nil, errors.New("identity document pendingTime is too old")
}
}
payload.document = doc payload.document = doc
return &payload, nil return &payload, nil
} }

View file

@ -150,6 +150,7 @@ func TestAWS_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
@ -157,6 +158,7 @@ func TestAWS_Init(t *testing.T) {
Accounts []string Accounts []string
DisableCustomSANs bool DisableCustomSANs bool
DisableTrustOnFirstUse bool DisableTrustOnFirstUse bool
InstanceAge Duration
Claims *Claims Claims *Claims
} }
type args struct { type args struct {
@ -168,10 +170,12 @@ func TestAWS_Init(t *testing.T) {
args args args args
wantErr bool wantErr bool
}{ }{
{"ok", fields{"AWS", "name", []string{"account"}, false, false, nil}, args{config}, false}, {"ok", fields{"AWS", "name", []string{"account"}, false, false, zero, nil}, args{config}, false},
{"fail type ", fields{"", "name", []string{"account"}, false, false, nil}, args{config}, true}, {"ok", fields{"AWS", "name", []string{"account"}, true, true, Duration{Duration: 1 * time.Minute}, nil}, args{config}, false},
{"fail name", fields{"AWS", "", []string{"account"}, false, false, nil}, args{config}, true}, {"fail type ", fields{"", "name", []string{"account"}, false, false, zero, nil}, args{config}, true},
{"fail claims", fields{"AWS", "name", []string{"account"}, false, false, badClaims}, args{config}, true}, {"fail name", fields{"AWS", "", []string{"account"}, false, false, zero, nil}, args{config}, true},
{"bad instance age", fields{"AWS", "name", []string{"account"}, false, false, Duration{Duration: -1 * time.Minute}, nil}, args{config}, true},
{"fail claims", fields{"AWS", "name", []string{"account"}, false, false, zero, badClaims}, args{config}, 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) {
@ -181,6 +185,7 @@ func TestAWS_Init(t *testing.T) {
Accounts: tt.fields.Accounts, Accounts: tt.fields.Accounts,
DisableCustomSANs: tt.fields.DisableCustomSANs, DisableCustomSANs: tt.fields.DisableCustomSANs,
DisableTrustOnFirstUse: tt.fields.DisableTrustOnFirstUse, DisableTrustOnFirstUse: tt.fields.DisableTrustOnFirstUse,
InstanceAge: tt.fields.InstanceAge,
Claims: tt.fields.Claims, Claims: tt.fields.Claims,
} }
if err := p.Init(tt.args.config); (err != nil) != tt.wantErr { if err := p.Init(tt.args.config); (err != nil) != tt.wantErr {
@ -200,6 +205,7 @@ func TestAWS_AuthorizeSign(t *testing.T) {
p2.Accounts = p1.Accounts p2.Accounts = p1.Accounts
p2.config = p1.config p2.config = p1.config
p2.DisableCustomSANs = true p2.DisableCustomSANs = true
p2.InstanceAge = Duration{1 * time.Minute}
p3, err := generateAWS() p3, err := generateAWS()
assert.FatalError(t, err) assert.FatalError(t, err)
@ -266,6 +272,10 @@ func TestAWS_AuthorizeSign(t *testing.T) {
"instance-id", awsIssuer, p1.GetID(), p1.Accounts[0], "instance-id", "instance-id", awsIssuer, p1.GetID(), p1.Accounts[0], "instance-id",
"127.0.0.1", "us-west-1", time.Now(), badKey) "127.0.0.1", "us-west-1", time.Now(), badKey)
assert.FatalError(t, err) assert.FatalError(t, err)
failInstanceAge, err := generateAWSToken(
"instance-id", awsIssuer, p2.GetID(), p2.Accounts[0], "instance-id",
"127.0.0.1", "us-west-1", time.Now().Add(-1*time.Minute), key)
assert.FatalError(t, err)
type args struct { type args struct {
token string token string
@ -292,6 +302,7 @@ func TestAWS_AuthorizeSign(t *testing.T) {
{"fail exp", p1, args{failExp}, 0, true}, {"fail exp", p1, args{failExp}, 0, true},
{"fail nbf", p1, args{failNbf}, 0, true}, {"fail nbf", p1, args{failNbf}, 0, true},
{"fail key", p1, args{failKey}, 0, true}, {"fail key", p1, args{failKey}, 0, true},
{"fail instance age", p2, args{failInstanceAge}, 0, 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) {

View file

@ -531,7 +531,7 @@ func generateAWSToken(sub, iss, aud, accountID, instanceID, privateIP, region st
ImageID: "ami-123123", ImageID: "ami-123123",
InstanceID: instanceID, InstanceID: instanceID,
InstanceType: "t2.micro", InstanceType: "t2.micro",
PendingTime: time.Now(), PendingTime: iat,
PrivateIP: privateIP, PrivateIP: privateIP,
Region: region, Region: region,
Version: "2017-09-30", Version: "2017-09-30",