forked from TrueCloudLab/certificates
Merge pull request #84 from smallstep/iid-common-name
Allow custom common names in cloud identity provisioners
This commit is contained in:
commit
5356bce4d8
11 changed files with 185 additions and 280 deletions
194
Gopkg.lock
generated
194
Gopkg.lock
generated
|
@ -66,14 +66,6 @@
|
|||
pruneopts = "UT"
|
||||
revision = "6a90982ecee230ff6cba02d5bd386acc030be9d3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2cd7915ab26ede7d95b8749e6b1f933f1c6d5398030684e6505940a10f31cfda"
|
||||
name = "github.com/ghodss/yaml"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:81fda4d18a16651bf92245ce5d6178cdd99f918db30ae9794732655f0686e895"
|
||||
|
@ -90,17 +82,6 @@
|
|||
revision = "72cd26f257d44c1114970e19afddcd812016007e"
|
||||
version = "v1.4.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b402bb9a24d108a9405a6f34675091b036c8b056aac843bf6ef2389a65c5cf48"
|
||||
name = "github.com/gogo/protobuf"
|
||||
packages = [
|
||||
"proto",
|
||||
"sortkeys",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "4cbf7e384e768b4e01799441fdf2a706a5635ae7"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "travis-1.9"
|
||||
digest = "1:e8f5d9c09a7209c740e769713376abda388c41b777ba8e9ed52767e21acf379f"
|
||||
|
@ -113,27 +94,13 @@
|
|||
revision = "883fe33ffc4344bad1ecd881f61afd5ec5d80e0a"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4c0989ca0bcd10799064318923b9bc2db6b4d6338dd75f3f2d86c3511aaaf5cf"
|
||||
digest = "1:97df918963298c287643883209a2c3f642e6593379f97ab400c2a2e219ab647d"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = [
|
||||
"proto",
|
||||
"ptypes",
|
||||
"ptypes/any",
|
||||
"ptypes/duration",
|
||||
"ptypes/timestamp",
|
||||
]
|
||||
packages = ["proto"]
|
||||
pruneopts = "UT"
|
||||
revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:3ee90c0d94da31b442dde97c99635aaafec68d0b8a3c12ee2075c6bdabeec6bb"
|
||||
name = "github.com/google/gofuzz"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:750e747d0aad97b79f4a4e00034bae415c2ea793fd9e61438d966ee9c79579bf"
|
||||
|
@ -150,14 +117,6 @@
|
|||
pruneopts = "UT"
|
||||
revision = "1003c8bd00dc2869cb5ca5282e6ce33834fed514"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3e551bbb3a7c0ab2a2bf4660e7fcad16db089fdcfbb44b0199e62838038623ea"
|
||||
name = "github.com/json-iterator/go"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "1624edc4454b8682399def8740d46db5e4362ba4"
|
||||
version = "v1.1.5"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:e51f40f0c19b39c1825eadd07d5c0a98a2ad5942b166d9fc4f54750ce9a04810"
|
||||
|
@ -235,22 +194,6 @@
|
|||
pruneopts = "UT"
|
||||
revision = "2e7d06bc7ada2979f17ccf8ebf486dba23b84fc7"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:33422d238f147d247752996a26574ac48dcf472976eda7f5134015f06bf16563"
|
||||
name = "github.com/modern-go/concurrent"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
|
||||
version = "1.0.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e32bdbdb7c377a07a9a46378290059822efdce5c8d96fe71940d87cb4f918855"
|
||||
name = "github.com/modern-go/reflect2"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd"
|
||||
version = "1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:266d082179f3a29a4bdcf1dcc49d4a304f5c7107e65bd22d1fecacf45f1ac348"
|
||||
name = "github.com/newrelic/go-agent"
|
||||
|
@ -493,54 +436,6 @@
|
|||
revision = "54a98f90d1c46b7731eb8fb305d2a321c30ef610"
|
||||
version = "v1.5.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:077c1c599507b3b3e9156d17d36e1e61928ee9b53a5b420f10f28ebd4a0b275c"
|
||||
name = "google.golang.org/genproto"
|
||||
packages = ["googleapis/rpc/status"]
|
||||
pruneopts = "UT"
|
||||
revision = "4b09977fb92221987e99d190c8f88f2c92727a29"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9ab5a33d8cb5c120602a34d2e985ce17956a4e8c2edce7e6961568f95e40c09a"
|
||||
name = "google.golang.org/grpc"
|
||||
packages = [
|
||||
".",
|
||||
"balancer",
|
||||
"balancer/base",
|
||||
"balancer/roundrobin",
|
||||
"binarylog/grpc_binarylog_v1",
|
||||
"codes",
|
||||
"connectivity",
|
||||
"credentials",
|
||||
"credentials/internal",
|
||||
"encoding",
|
||||
"encoding/proto",
|
||||
"grpclog",
|
||||
"internal",
|
||||
"internal/backoff",
|
||||
"internal/binarylog",
|
||||
"internal/channelz",
|
||||
"internal/envconfig",
|
||||
"internal/grpcrand",
|
||||
"internal/grpcsync",
|
||||
"internal/syscall",
|
||||
"internal/transport",
|
||||
"keepalive",
|
||||
"metadata",
|
||||
"naming",
|
||||
"peer",
|
||||
"resolver",
|
||||
"resolver/dns",
|
||||
"resolver/passthrough",
|
||||
"stats",
|
||||
"status",
|
||||
"tap",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "a02b0774206b209466313a0b525d2c738fe407eb"
|
||||
version = "v1.18.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:39efb07a0d773dc09785b237ada4e10b5f28646eb6505d97bc18f8d2ff439362"
|
||||
name = "gopkg.in/alecthomas/kingpin.v3-unstable"
|
||||
|
@ -548,14 +443,6 @@
|
|||
pruneopts = "UT"
|
||||
revision = "63abe20a23e29e80bbef8089bd3dee3ac25e5306"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2d1fbdc6777e5408cabeb02bf336305e724b925ff4546ded0fa8715a7267922a"
|
||||
name = "gopkg.in/inf.v0"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf"
|
||||
version = "v0.9.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9593bab40e981b1f90b7e07faeab0d09b75fe338880d08880f986a9d3283c53f"
|
||||
name = "gopkg.in/square/go-jose.v2"
|
||||
|
@ -577,82 +464,14 @@
|
|||
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
||||
version = "v2.2.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:767b6c0b2c1d9487ee50cb8df1d0fdebf06ac0b19b723f6489d388e7b47c962d"
|
||||
name = "k8s.io/api"
|
||||
packages = [
|
||||
"admission/v1beta1",
|
||||
"authentication/v1",
|
||||
"core/v1",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "de494049e22a9ccf748c5bbda7492f42f344d0cd"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:5eb353533eaebdfec2392210ab218a389965ba5d4dc02b4aef87b9549e5d0f84"
|
||||
name = "k8s.io/apimachinery"
|
||||
packages = [
|
||||
"pkg/api/resource",
|
||||
"pkg/apis/meta/v1",
|
||||
"pkg/apis/meta/v1/unstructured",
|
||||
"pkg/conversion",
|
||||
"pkg/conversion/queryparams",
|
||||
"pkg/fields",
|
||||
"pkg/labels",
|
||||
"pkg/runtime",
|
||||
"pkg/runtime/schema",
|
||||
"pkg/runtime/serializer",
|
||||
"pkg/runtime/serializer/json",
|
||||
"pkg/runtime/serializer/protobuf",
|
||||
"pkg/runtime/serializer/recognizer",
|
||||
"pkg/runtime/serializer/versioning",
|
||||
"pkg/selection",
|
||||
"pkg/types",
|
||||
"pkg/util/errors",
|
||||
"pkg/util/framer",
|
||||
"pkg/util/intstr",
|
||||
"pkg/util/json",
|
||||
"pkg/util/naming",
|
||||
"pkg/util/net",
|
||||
"pkg/util/runtime",
|
||||
"pkg/util/sets",
|
||||
"pkg/util/validation",
|
||||
"pkg/util/validation/field",
|
||||
"pkg/util/yaml",
|
||||
"pkg/watch",
|
||||
"third_party/forked/golang/reflect",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "4b3b852955ebe47857fcf134b531b23dd8f3e793"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:72fd56341405f53c745377e0ebc4abeff87f1a048e0eea6568a20212650f5a82"
|
||||
name = "k8s.io/klog"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "71442cd4037d612096940ceb0f3fec3f7fff66e0"
|
||||
version = "v0.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7719608fe0b52a4ece56c2dde37bedd95b938677d1ab0f84b8a7852e4c59f849"
|
||||
name = "sigs.k8s.io/yaml"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "fd68e9863619f6ec2fdd8625fe1f02e7c877e480"
|
||||
version = "v1.1.0"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/alecthomas/gometalinter",
|
||||
"github.com/client9/misspell/cmd/misspell",
|
||||
"github.com/ghodss/yaml",
|
||||
"github.com/go-chi/chi",
|
||||
"github.com/golang/lint/golint",
|
||||
"github.com/golang/protobuf/proto",
|
||||
"github.com/gordonklaus/ineffassign",
|
||||
"github.com/newrelic/go-agent",
|
||||
"github.com/pkg/errors",
|
||||
|
@ -675,18 +494,9 @@
|
|||
"github.com/tsenart/deadcode",
|
||||
"github.com/urfave/cli",
|
||||
"golang.org/x/crypto/ocsp",
|
||||
"golang.org/x/net/context",
|
||||
"golang.org/x/net/http2",
|
||||
"google.golang.org/grpc",
|
||||
"google.golang.org/grpc/credentials",
|
||||
"google.golang.org/grpc/peer",
|
||||
"gopkg.in/square/go-jose.v2",
|
||||
"gopkg.in/square/go-jose.v2/jwt",
|
||||
"k8s.io/api/admission/v1beta1",
|
||||
"k8s.io/api/core/v1",
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"k8s.io/apimachinery/pkg/runtime",
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
|
@ -171,7 +171,7 @@ func (p *AWS) GetEncryptedKey() (kid string, key string, ok bool) {
|
|||
|
||||
// GetIdentityToken retrieves the identity document and it's signature and
|
||||
// generates a token with them.
|
||||
func (p *AWS) GetIdentityToken(caURL string) (string, error) {
|
||||
func (p *AWS) GetIdentityToken(subject, caURL string) (string, error) {
|
||||
// Initialize the config if this method is used from the cli.
|
||||
if err := p.assertConfig(); err != nil {
|
||||
return "", err
|
||||
|
@ -221,7 +221,7 @@ func (p *AWS) GetIdentityToken(caURL string) (string, error) {
|
|||
payload := awsPayload{
|
||||
Claims: jose.Claims{
|
||||
Issuer: awsIssuer,
|
||||
Subject: idoc.InstanceID,
|
||||
Subject: subject,
|
||||
Audience: []string{audience},
|
||||
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
|
@ -273,8 +273,8 @@ func (p *AWS) AuthorizeSign(token string) ([]SignOption, error) {
|
|||
}
|
||||
doc := payload.document
|
||||
|
||||
// Enforce default DNS and IP if configured.
|
||||
// By default we'll accept the SANs in the CSR.
|
||||
// Enforce known CN and default DNS and IP if configured.
|
||||
// By default we'll accept the CN and SANs in the CSR.
|
||||
// There's no way to trust them other than TOFU.
|
||||
var so []SignOption
|
||||
if p.DisableCustomSANs {
|
||||
|
@ -287,9 +287,9 @@ func (p *AWS) AuthorizeSign(token string) ([]SignOption, error) {
|
|||
}
|
||||
|
||||
return append(so,
|
||||
commonNameValidator(doc.InstanceID),
|
||||
commonNameValidator(payload.Claims.Subject),
|
||||
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),
|
||||
newProvisionerExtensionOption(TypeAWS, p.Name, doc.AccountID),
|
||||
newProvisionerExtensionOption(TypeAWS, p.Name, doc.AccountID, "InstanceID", doc.InstanceID),
|
||||
newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()),
|
||||
), nil
|
||||
}
|
||||
|
@ -388,19 +388,26 @@ func (p *AWS) authorizeToken(token string) (*awsPayload, error) {
|
|||
// more than a few minutes.
|
||||
now := time.Now().UTC()
|
||||
if err = payload.ValidateWithLeeway(jose.Expected{
|
||||
Issuer: awsIssuer,
|
||||
Subject: doc.InstanceID,
|
||||
Time: now,
|
||||
Issuer: awsIssuer,
|
||||
Time: now,
|
||||
}, time.Minute); err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid token")
|
||||
}
|
||||
|
||||
// validate audiences with the defaults
|
||||
if !matchesAudience(payload.Audience, p.audiences.Sign) {
|
||||
fmt.Println(payload.Audience, "vs", p.audiences.Sign)
|
||||
return nil, errors.New("invalid token: invalid audience claim (aud)")
|
||||
}
|
||||
|
||||
// Validate subject, it has to be known if disableCustomSANs is enabled
|
||||
if p.DisableCustomSANs {
|
||||
if payload.Subject != doc.InstanceID &&
|
||||
payload.Subject != doc.PrivateIP &&
|
||||
payload.Subject != fmt.Sprintf("ip-%s.%s.compute.internal", strings.Replace(doc.PrivateIP, ".", "-", -1), doc.Region) {
|
||||
return nil, errors.New("invalid token: invalid subject claim (sub)")
|
||||
}
|
||||
}
|
||||
|
||||
// validate accounts
|
||||
if len(p.Accounts) > 0 {
|
||||
var found bool
|
||||
|
|
|
@ -48,14 +48,14 @@ func TestAWS_GetTokenID(t *testing.T) {
|
|||
p2.config = p1.config
|
||||
p2.DisableTrustOnFirstUse = true
|
||||
|
||||
t1, err := p1.GetIdentityToken("https://ca.smallstep.com")
|
||||
t1, err := p1.GetIdentityToken("foo.local", "https://ca.smallstep.com")
|
||||
assert.FatalError(t, err)
|
||||
_, claims, err := parseAWSToken(t1)
|
||||
assert.FatalError(t, err)
|
||||
sum := sha256.Sum256([]byte(fmt.Sprintf("%s.%s", p1.GetID(), claims.document.InstanceID)))
|
||||
w1 := strings.ToLower(hex.EncodeToString(sum[:]))
|
||||
|
||||
t2, err := p2.GetIdentityToken("https://ca.smallstep.com")
|
||||
t2, err := p2.GetIdentityToken("foo.local", "https://ca.smallstep.com")
|
||||
assert.FatalError(t, err)
|
||||
sum = sha256.Sum256([]byte(t2))
|
||||
w2 := strings.ToLower(hex.EncodeToString(sum[:]))
|
||||
|
@ -111,12 +111,31 @@ func TestAWS_GetIdentityToken(t *testing.T) {
|
|||
p4.config.signatureURL = srv.URL + "/bad-signature"
|
||||
p4.config.identityURL = p1.config.identityURL
|
||||
|
||||
p5, err := generateAWS()
|
||||
assert.FatalError(t, err)
|
||||
p5.Accounts = p1.Accounts
|
||||
p5.config.identityURL = "https://1234.1234.1234.1234"
|
||||
p5.config.signatureURL = p1.config.signatureURL
|
||||
|
||||
p6, err := generateAWS()
|
||||
assert.FatalError(t, err)
|
||||
p6.Accounts = p1.Accounts
|
||||
p6.config.identityURL = p1.config.identityURL
|
||||
p6.config.signatureURL = "https://1234.1234.1234.1234"
|
||||
|
||||
p7, err := generateAWS()
|
||||
assert.FatalError(t, err)
|
||||
p7.Accounts = p1.Accounts
|
||||
p7.config.identityURL = srv.URL + "/bad-json"
|
||||
p7.config.signatureURL = p1.config.signatureURL
|
||||
|
||||
caURL := "https://ca.smallstep.com"
|
||||
u, err := url.Parse(caURL)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
type args struct {
|
||||
caURL string
|
||||
subject string
|
||||
caURL string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -124,15 +143,18 @@ func TestAWS_GetIdentityToken(t *testing.T) {
|
|||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{"ok", p1, args{caURL}, false},
|
||||
{"fail ca url", p1, args{"://ca.smallstep.com"}, true},
|
||||
{"fail identityURL", p2, args{caURL}, true},
|
||||
{"fail signatureURL", p3, args{caURL}, true},
|
||||
{"fail signature", p4, args{caURL}, true},
|
||||
{"ok", p1, args{"foo.local", caURL}, false},
|
||||
{"fail ca url", p1, args{"foo.local", "://ca.smallstep.com"}, true},
|
||||
{"fail identityURL", p2, args{"foo.local", caURL}, true},
|
||||
{"fail signatureURL", p3, args{"foo.local", caURL}, true},
|
||||
{"fail signature", p4, args{"foo.local", caURL}, true},
|
||||
{"fail read identityURL", p5, args{"foo.local", caURL}, true},
|
||||
{"fail read signatureURL", p6, args{"foo.local", caURL}, true},
|
||||
{"fail unmarshal identityURL", p7, args{"foo.local", caURL}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.aws.GetIdentityToken(tt.args.caURL)
|
||||
got, err := tt.aws.GetIdentityToken(tt.args.subject, tt.args.caURL)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("AWS.GetIdentityToken() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
|
@ -141,7 +163,7 @@ func TestAWS_GetIdentityToken(t *testing.T) {
|
|||
_, c, err := parseAWSToken(got)
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equals(t, awsIssuer, c.Issuer)
|
||||
assert.Equals(t, c.document.InstanceID, c.Subject)
|
||||
assert.Equals(t, tt.args.subject, c.Subject)
|
||||
assert.Equals(t, jose.Audience{u.ResolveReference(&url.URL{Path: "/1.0/sign", Fragment: tt.aws.GetID()}).String()}, c.Audience)
|
||||
assert.Equals(t, tt.aws.Accounts[0], c.document.AccountID)
|
||||
err = tt.aws.config.certificate.CheckSignature(
|
||||
|
@ -221,11 +243,18 @@ func TestAWS_AuthorizeSign(t *testing.T) {
|
|||
assert.FatalError(t, err)
|
||||
p3.config = p1.config
|
||||
|
||||
t1, err := p1.GetIdentityToken("https://ca.smallstep.com")
|
||||
t1, err := p1.GetIdentityToken("foo.local", "https://ca.smallstep.com")
|
||||
assert.FatalError(t, err)
|
||||
t2, err := p2.GetIdentityToken("https://ca.smallstep.com")
|
||||
t2, err := p2.GetIdentityToken("instance-id", "https://ca.smallstep.com")
|
||||
assert.FatalError(t, err)
|
||||
t3, err := p3.GetIdentityToken("https://ca.smallstep.com")
|
||||
assert.FatalError(t, err)
|
||||
t3, err := p3.GetIdentityToken("foo.local", "https://ca.smallstep.com")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
// Alternative common names with DisableCustomSANs = true
|
||||
t2PrivateIP, err := p2.GetIdentityToken("127.0.0.1", "https://ca.smallstep.com")
|
||||
assert.FatalError(t, err)
|
||||
t2Hostname, err := p2.GetIdentityToken("ip-127-0-0-1.us-west-1.compute.internal", "https://ca.smallstep.com")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
block, _ := pem.Decode([]byte(awsTestKey))
|
||||
|
@ -243,7 +272,7 @@ func TestAWS_AuthorizeSign(t *testing.T) {
|
|||
"127.0.0.1", "us-west-1", time.Now(), key)
|
||||
assert.FatalError(t, err)
|
||||
failSubject, err := generateAWSToken(
|
||||
"bad-subject", awsIssuer, p1.GetID(), p1.Accounts[0], "instance-id",
|
||||
"bad-subject", awsIssuer, p2.GetID(), p2.Accounts[0], "instance-id",
|
||||
"127.0.0.1", "us-west-1", time.Now(), key)
|
||||
assert.FatalError(t, err)
|
||||
failIssuer, err := generateAWSToken(
|
||||
|
@ -299,6 +328,8 @@ func TestAWS_AuthorizeSign(t *testing.T) {
|
|||
}{
|
||||
{"ok", p1, args{t1}, 4, false},
|
||||
{"ok", p2, args{t2}, 6, false},
|
||||
{"ok", p2, args{t2Hostname}, 6, false},
|
||||
{"ok", p2, args{t2PrivateIP}, 6, false},
|
||||
{"ok", p1, args{t4}, 4, false},
|
||||
{"fail account", p3, args{t3}, 0, true},
|
||||
{"fail token", p1, args{"token"}, 0, true},
|
||||
|
@ -364,7 +395,7 @@ func TestAWS_AuthorizeRevoke(t *testing.T) {
|
|||
assert.FatalError(t, err)
|
||||
defer srv.Close()
|
||||
|
||||
t1, err := p1.GetIdentityToken("https://ca.smallstep.com")
|
||||
t1, err := p1.GetIdentityToken("foo.local", "https://ca.smallstep.com")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
type args struct {
|
||||
|
|
|
@ -141,7 +141,7 @@ func (p *Azure) GetEncryptedKey() (kid string, key string, ok bool) {
|
|||
|
||||
// GetIdentityToken retrieves from the metadata service the identity token and
|
||||
// returns it.
|
||||
func (p *Azure) GetIdentityToken() (string, error) {
|
||||
func (p *Azure) GetIdentityToken(subject, caURL string) (string, error) {
|
||||
// Initialize the config if this method is used from the cli.
|
||||
p.assertConfig()
|
||||
|
||||
|
@ -264,17 +264,17 @@ func (p *Azure) AuthorizeSign(token string) ([]SignOption, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Enforce default DNS if configured.
|
||||
// By default we'll accept the SANs in the CSR.
|
||||
// Enforce known common name and default DNS if configured.
|
||||
// By default we'll accept the CN and SANs in the CSR.
|
||||
// There's no way to trust them other than TOFU.
|
||||
var so []SignOption
|
||||
if p.DisableCustomSANs {
|
||||
// name will work only inside the virtual network
|
||||
so = append(so, commonNameValidator(name))
|
||||
so = append(so, dnsNamesValidator([]string{name}))
|
||||
}
|
||||
|
||||
return append(so,
|
||||
commonNameValidator(name),
|
||||
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),
|
||||
newProvisionerExtensionOption(TypeAzure, p.Name, p.TenantID),
|
||||
newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()),
|
||||
|
|
|
@ -46,9 +46,9 @@ func TestAzure_GetTokenID(t *testing.T) {
|
|||
p2.keyStore = p1.keyStore
|
||||
p2.DisableTrustOnFirstUse = true
|
||||
|
||||
t1, err := p1.GetIdentityToken()
|
||||
t1, err := p1.GetIdentityToken("subject", "caURL")
|
||||
assert.FatalError(t, err)
|
||||
t2, err := p2.GetIdentityToken()
|
||||
t2, err := p2.GetIdentityToken("subject", "caURL")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
sum := sha256.Sum256([]byte("/subscriptions/subscriptionID/resourceGroups/resourceGroup/providers/Microsoft.Compute/virtualMachines/virtualMachine"))
|
||||
|
@ -105,23 +105,28 @@ func TestAzure_GetIdentityToken(t *testing.T) {
|
|||
}))
|
||||
defer srv.Close()
|
||||
|
||||
type args struct {
|
||||
subject string
|
||||
caURL string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
azure *Azure
|
||||
args args
|
||||
identityTokenURL string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{"ok", p1, srv.URL, t1, false},
|
||||
{"fail request", p1, srv.URL + "/bad-request", "", true},
|
||||
{"fail unmarshal", p1, srv.URL + "/bad-json", "", true},
|
||||
{"fail url", p1, "://ca.smallstep.com", "", true},
|
||||
{"fail connect", p1, "foobarzar", "", true},
|
||||
{"ok", p1, args{"subject", "caURL"}, srv.URL, t1, false},
|
||||
{"fail request", p1, args{"subject", "caURL"}, srv.URL + "/bad-request", "", true},
|
||||
{"fail unmarshal", p1, args{"subject", "caURL"}, srv.URL + "/bad-json", "", true},
|
||||
{"fail url", p1, args{"subject", "caURL"}, "://ca.smallstep.com", "", true},
|
||||
{"fail connect", p1, args{"subject", "caURL"}, "foobarzar", "", true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.azure.config.identityTokenURL = tt.identityTokenURL
|
||||
got, err := tt.azure.GetIdentityToken()
|
||||
got, err := tt.azure.GetIdentityToken(tt.args.subject, tt.args.caURL)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Azure.GetIdentityToken() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
|
@ -231,13 +236,13 @@ func TestAzure_AuthorizeSign(t *testing.T) {
|
|||
badKey, err := generateJSONWebKey()
|
||||
assert.FatalError(t, err)
|
||||
|
||||
t1, err := p1.GetIdentityToken()
|
||||
t1, err := p1.GetIdentityToken("subject", "caURL")
|
||||
assert.FatalError(t, err)
|
||||
t2, err := p2.GetIdentityToken()
|
||||
t2, err := p2.GetIdentityToken("subject", "caURL")
|
||||
assert.FatalError(t, err)
|
||||
t3, err := p3.GetIdentityToken()
|
||||
t3, err := p3.GetIdentityToken("subject", "caURL")
|
||||
assert.FatalError(t, err)
|
||||
t4, err := p4.GetIdentityToken()
|
||||
t4, err := p4.GetIdentityToken("subject", "caURL")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
t11, err := generateAzureToken("subject", p1.oidcConfig.Issuer, azureDefaultAudience,
|
||||
|
@ -276,9 +281,9 @@ func TestAzure_AuthorizeSign(t *testing.T) {
|
|||
wantLen int
|
||||
wantErr bool
|
||||
}{
|
||||
{"ok", p1, args{t1}, 4, false},
|
||||
{"ok", p1, args{t1}, 3, false},
|
||||
{"ok", p2, args{t2}, 5, false},
|
||||
{"ok", p1, args{t11}, 4, false},
|
||||
{"ok", p1, args{t11}, 3, false},
|
||||
{"fail tenant", p3, args{t3}, 0, true},
|
||||
{"fail resource group", p4, args{t4}, 0, true},
|
||||
{"fail token", p1, args{"token"}, 0, true},
|
||||
|
@ -338,7 +343,7 @@ func TestAzure_AuthorizeRevoke(t *testing.T) {
|
|||
assert.FatalError(t, err)
|
||||
defer srv.Close()
|
||||
|
||||
token, err := az.GetIdentityToken()
|
||||
token, err := az.GetIdentityToken("subject", "caURL")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
type args struct {
|
||||
|
|
|
@ -150,7 +150,7 @@ func (p *GCP) GetIdentityURL(audience string) string {
|
|||
}
|
||||
|
||||
// GetIdentityToken does an HTTP request to the identity url.
|
||||
func (p *GCP) GetIdentityToken(caURL string) (string, error) {
|
||||
func (p *GCP) GetIdentityToken(subject, caURL string) (string, error) {
|
||||
audience, err := generateSignAudience(caURL, p.GetID())
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -212,21 +212,24 @@ func (p *GCP) AuthorizeSign(token string) ([]SignOption, error) {
|
|||
}
|
||||
ce := claims.Google.ComputeEngine
|
||||
|
||||
// Enforce default DNS if configured.
|
||||
// By default we we'll accept the SANs in the CSR.
|
||||
// Enforce known common name and default DNS if configured.
|
||||
// By default we we'll accept the CN and SANs in the CSR.
|
||||
// There's no way to trust them other than TOFU.
|
||||
var so []SignOption
|
||||
if p.DisableCustomSANs {
|
||||
dnsName1 := fmt.Sprintf("%s.c.%s.internal", ce.InstanceName, ce.ProjectID)
|
||||
dnsName2 := fmt.Sprintf("%s.%s.c.%s.internal", ce.InstanceName, ce.Zone, ce.ProjectID)
|
||||
so = append(so, commonNameSliceValidator([]string{
|
||||
ce.InstanceName, ce.InstanceID, dnsName1, dnsName2,
|
||||
}))
|
||||
so = append(so, dnsNamesValidator([]string{
|
||||
fmt.Sprintf("%s.c.%s.internal", ce.InstanceName, ce.ProjectID),
|
||||
fmt.Sprintf("%s.%s.c.%s.internal", ce.InstanceName, ce.Zone, ce.ProjectID),
|
||||
dnsName1, dnsName2,
|
||||
}))
|
||||
}
|
||||
|
||||
return append(so,
|
||||
commonNameValidator(ce.InstanceName),
|
||||
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),
|
||||
newProvisionerExtensionOption(TypeGCP, p.Name, claims.Subject),
|
||||
newProvisionerExtensionOption(TypeGCP, p.Name, claims.Subject, "InstanceID", ce.InstanceID, "InstanceName", ce.InstanceName),
|
||||
newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()),
|
||||
), nil
|
||||
}
|
||||
|
|
|
@ -117,7 +117,8 @@ func TestGCP_GetIdentityToken(t *testing.T) {
|
|||
defer srv.Close()
|
||||
|
||||
type args struct {
|
||||
caURL string
|
||||
subject string
|
||||
caURL string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -127,16 +128,16 @@ func TestGCP_GetIdentityToken(t *testing.T) {
|
|||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{"ok", p1, args{"https://ca"}, srv.URL, t1, false},
|
||||
{"fail ca url", p1, args{"://ca"}, srv.URL, "", true},
|
||||
{"fail request", p1, args{"https://ca"}, srv.URL + "/bad-request", "", true},
|
||||
{"fail url", p1, args{"https://ca"}, "://ca.smallstep.com", "", true},
|
||||
{"fail connect", p1, args{"https://ca"}, "foobarzar", "", true},
|
||||
{"ok", p1, args{"subject", "https://ca"}, srv.URL, t1, false},
|
||||
{"fail ca url", p1, args{"subject", "://ca"}, srv.URL, "", true},
|
||||
{"fail request", p1, args{"subject", "https://ca"}, srv.URL + "/bad-request", "", true},
|
||||
{"fail url", p1, args{"subject", "https://ca"}, "://ca.smallstep.com", "", true},
|
||||
{"fail connect", p1, args{"subject", "https://ca"}, "foobarzar", "", true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.gcp.config.IdentityURL = tt.identityURL
|
||||
got, err := tt.gcp.GetIdentityToken(tt.args.caURL)
|
||||
got, err := tt.gcp.GetIdentityToken(tt.args.subject, tt.args.caURL)
|
||||
t.Log(err)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GCP.GetIdentityToken() error = %v, wantErr %v", err, tt.wantErr)
|
||||
|
@ -310,9 +311,9 @@ func TestGCP_AuthorizeSign(t *testing.T) {
|
|||
wantLen int
|
||||
wantErr bool
|
||||
}{
|
||||
{"ok", p1, args{t1}, 4, false},
|
||||
{"ok", p1, args{t1}, 3, false},
|
||||
{"ok", p2, args{t2}, 5, false},
|
||||
{"ok", p3, args{t3}, 4, false},
|
||||
{"ok", p3, args{t3}, 3, false},
|
||||
{"fail token", p1, args{"token"}, 0, true},
|
||||
{"fail key", p1, args{failKey}, 0, true},
|
||||
{"fail iss", p1, args{failIss}, 0, true},
|
||||
|
|
|
@ -55,7 +55,12 @@ func (v profileWithOption) Option(Options) x509util.WithOption {
|
|||
type profileDefaultDuration time.Duration
|
||||
|
||||
func (v profileDefaultDuration) Option(so Options) x509util.WithOption {
|
||||
return x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), time.Duration(v))
|
||||
notBefore := so.NotBefore.Time()
|
||||
if notBefore.IsZero() {
|
||||
notBefore = time.Now()
|
||||
}
|
||||
notAfter := so.NotAfter.RelativeTime(notBefore)
|
||||
return x509util.WithNotBeforeAfterDuration(notBefore, notAfter, time.Duration(v))
|
||||
}
|
||||
|
||||
// emailOnlyIdentity is a CertificateRequestValidator that checks that the only
|
||||
|
@ -97,6 +102,21 @@ func (v commonNameValidator) Valid(req *x509.CertificateRequest) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// commonNameSliceValidator validates thats the common name of a certificate request is present in the slice.
|
||||
type commonNameSliceValidator []string
|
||||
|
||||
func (v commonNameSliceValidator) Valid(req *x509.CertificateRequest) error {
|
||||
if req.Subject.CommonName == "" {
|
||||
return errors.New("certificate request cannot contain an empty common name")
|
||||
}
|
||||
for _, cn := range v {
|
||||
if req.Subject.CommonName == cn {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.Errorf("certificate request does not contain the valid common name, got %s, want %s", req.Subject.CommonName, v)
|
||||
}
|
||||
|
||||
// dnsNamesValidator validates the DNS names SAN of a certificate request.
|
||||
type dnsNamesValidator []string
|
||||
|
||||
|
@ -180,29 +200,32 @@ var (
|
|||
)
|
||||
|
||||
type stepProvisionerASN1 struct {
|
||||
Type int
|
||||
Name []byte
|
||||
CredentialID []byte
|
||||
Type int
|
||||
Name []byte
|
||||
CredentialID []byte
|
||||
KeyValuePairs []string `asn1:"optional,omitempty"`
|
||||
}
|
||||
|
||||
type provisionerExtensionOption struct {
|
||||
Type int
|
||||
Name string
|
||||
CredentialID string
|
||||
Type int
|
||||
Name string
|
||||
CredentialID string
|
||||
KeyValuePairs []string
|
||||
}
|
||||
|
||||
func newProvisionerExtensionOption(typ Type, name, credentialID string) *provisionerExtensionOption {
|
||||
func newProvisionerExtensionOption(typ Type, name, credentialID string, keyValuePairs ...string) *provisionerExtensionOption {
|
||||
return &provisionerExtensionOption{
|
||||
Type: int(typ),
|
||||
Name: name,
|
||||
CredentialID: credentialID,
|
||||
Type: int(typ),
|
||||
Name: name,
|
||||
CredentialID: credentialID,
|
||||
KeyValuePairs: keyValuePairs,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *provisionerExtensionOption) Option(Options) x509util.WithOption {
|
||||
return func(p x509util.Profile) error {
|
||||
crt := p.Subject()
|
||||
ext, err := createProvisionerExtension(o.Type, o.Name, o.CredentialID)
|
||||
ext, err := createProvisionerExtension(o.Type, o.Name, o.CredentialID, o.KeyValuePairs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -211,11 +234,12 @@ func (o *provisionerExtensionOption) Option(Options) x509util.WithOption {
|
|||
}
|
||||
}
|
||||
|
||||
func createProvisionerExtension(typ int, name, credentialID string) (pkix.Extension, error) {
|
||||
func createProvisionerExtension(typ int, name, credentialID string, keyValuePairs ...string) (pkix.Extension, error) {
|
||||
b, err := asn1.Marshal(stepProvisionerASN1{
|
||||
Type: typ,
|
||||
Name: []byte(name),
|
||||
CredentialID: []byte(credentialID),
|
||||
Type: typ,
|
||||
Name: []byte(name),
|
||||
CredentialID: []byte(credentialID),
|
||||
KeyValuePairs: keyValuePairs,
|
||||
})
|
||||
if err != nil {
|
||||
return pkix.Extension{}, errors.Wrapf(err, "error marshaling provisioner extension")
|
||||
|
|
|
@ -64,6 +64,30 @@ func Test_commonNameValidator_Valid(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_commonNameSliceValidator_Valid(t *testing.T) {
|
||||
type args struct {
|
||||
req *x509.CertificateRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
v commonNameSliceValidator
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{"ok", []string{"foo.bar.zar"}, args{&x509.CertificateRequest{Subject: pkix.Name{CommonName: "foo.bar.zar"}}}, false},
|
||||
{"ok", []string{"example.com", "foo.bar.zar"}, args{&x509.CertificateRequest{Subject: pkix.Name{CommonName: "foo.bar.zar"}}}, false},
|
||||
{"empty", []string{""}, args{&x509.CertificateRequest{Subject: pkix.Name{CommonName: ""}}}, true},
|
||||
{"wrong", []string{"foo.bar.zar"}, args{&x509.CertificateRequest{Subject: pkix.Name{CommonName: "example.com"}}}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := tt.v.Valid(tt.args.req); (err != nil) != tt.wantErr {
|
||||
t.Errorf("commonNameSliceValidator.Valid() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_dnsNamesValidator_Valid(t *testing.T) {
|
||||
type args struct {
|
||||
req *x509.CertificateRequest
|
||||
|
|
|
@ -104,6 +104,12 @@ func (t *TimeDuration) UnmarshalJSON(data []byte) error {
|
|||
|
||||
// Time calculates the embedded time.Time, sets it if necessary, and returns it.
|
||||
func (t *TimeDuration) Time() time.Time {
|
||||
return t.RelativeTime(now())
|
||||
}
|
||||
|
||||
// RelativeTime returns the embedded time.Time or the base time plus the
|
||||
// duration if this is not zero.
|
||||
func (t *TimeDuration) RelativeTime(base time.Time) time.Time {
|
||||
switch {
|
||||
case t == nil:
|
||||
return time.Time{}
|
||||
|
@ -111,8 +117,8 @@ func (t *TimeDuration) Time() time.Time {
|
|||
if t.d == 0 {
|
||||
return time.Time{}
|
||||
}
|
||||
t.t = now().Add(t.d)
|
||||
return t.t
|
||||
t.t = base.Add(t.d)
|
||||
return t.t.UTC()
|
||||
default:
|
||||
return t.t.UTC()
|
||||
}
|
||||
|
|
|
@ -283,20 +283,12 @@ func generateAWSWithServer() (*AWS, *httptest.Server, error) {
|
|||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "error parsing AWS private key")
|
||||
}
|
||||
instanceID, err := randutil.Alphanumeric(10)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
imageID, err := randutil.Alphanumeric(10)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
doc, err := json.MarshalIndent(awsInstanceIdentityDocument{
|
||||
AccountID: aws.Accounts[0],
|
||||
Architecture: "x86_64",
|
||||
AvailabilityZone: "us-west-2b",
|
||||
ImageID: imageID,
|
||||
InstanceID: instanceID,
|
||||
ImageID: "image-id",
|
||||
InstanceID: "instance-id",
|
||||
InstanceType: "t2.micro",
|
||||
PendingTime: time.Now(),
|
||||
PrivateIP: "127.0.0.1",
|
||||
|
@ -322,6 +314,8 @@ func generateAWSWithServer() (*AWS, *httptest.Server, error) {
|
|||
w.Write([]byte("{}"))
|
||||
case "/bad-signature":
|
||||
w.Write([]byte("YmFkLXNpZ25hdHVyZQo="))
|
||||
case "/bad-json":
|
||||
w.Write([]byte("{"))
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue