Skip to content

Commit 2ad2bd8

Browse files
committed
Define credential IDs for X.509 certificates
This commit expands the existing credential ID concept to cover X.509 certificates. We use the certificate's signature as the credential ID, since this safe and unique.
1 parent b510f78 commit 2ad2bd8

File tree

5 files changed

+93
-58
lines changed

5 files changed

+93
-58
lines changed

staging/src/k8s.io/apiserver/pkg/authentication/request/x509/x509.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package x509
1818

1919
import (
20+
"crypto/sha256"
2021
"crypto/x509"
2122
"crypto/x509/pkix"
2223
"encoding/hex"
@@ -276,10 +277,17 @@ var CommonNameUserConversion = UserConversionFunc(func(chain []*x509.Certificate
276277
if len(chain[0].Subject.CommonName) == 0 {
277278
return nil, false, nil
278279
}
280+
281+
fp := sha256.Sum256(chain[0].Raw)
282+
id := "X509SHA256=" + hex.EncodeToString(fp[:])
283+
279284
return &authenticator.Response{
280285
User: &user.DefaultInfo{
281286
Name: chain[0].Subject.CommonName,
282287
Groups: chain[0].Subject.Organization,
288+
Extra: map[string][]string{
289+
user.CredentialIDKey: {id},
290+
},
283291
},
284292
}, true, nil
285293
})

staging/src/k8s.io/apiserver/pkg/authentication/request/x509/x509_test.go

Lines changed: 75 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ import (
2323
"errors"
2424
"io/ioutil"
2525
"net/http"
26-
"reflect"
2726
"sort"
2827
"testing"
2928
"time"
3029

30+
"github.com/google/go-cmp/cmp"
3131
"k8s.io/apimachinery/pkg/util/sets"
3232
"k8s.io/apiserver/pkg/authentication/authenticator"
3333
"k8s.io/apiserver/pkg/authentication/user"
@@ -581,9 +581,8 @@ func TestX509(t *testing.T) {
581581
Opts x509.VerifyOptions
582582
User UserConversion
583583

584-
ExpectUserName string
585-
ExpectGroups []string
586584
ExpectOK bool
585+
ExpectResponse *authenticator.Response
587586
ExpectErr bool
588587
}{
589588
"non-tls": {
@@ -618,21 +617,35 @@ func TestX509(t *testing.T) {
618617
Certs: getCerts(t, serverCert),
619618
User: CommonNameUserConversion,
620619

621-
ExpectUserName: "127.0.0.1",
622-
ExpectGroups: []string{"My Org"},
623-
ExpectOK: true,
624-
ExpectErr: false,
620+
ExpectOK: true,
621+
ExpectResponse: &authenticator.Response{
622+
User: &user.DefaultInfo{
623+
Name: "127.0.0.1",
624+
Groups: []string{"My Org"},
625+
Extra: map[string][]string{
626+
user.CredentialIDKey: {"X509SHA256=92209d1e0dd36a018f244f5e1b88e2d47b049e9cfcd4b7c87c65875866872230"},
627+
},
628+
},
629+
},
630+
ExpectErr: false,
625631
},
626632

627633
"common name": {
628634
Opts: getDefaultVerifyOptions(t),
629635
Certs: getCerts(t, clientCNCert),
630636
User: CommonNameUserConversion,
631637

632-
ExpectUserName: "client_cn",
633-
ExpectGroups: []string{"My Org"},
634-
ExpectOK: true,
635-
ExpectErr: false,
638+
ExpectOK: true,
639+
ExpectResponse: &authenticator.Response{
640+
User: &user.DefaultInfo{
641+
Name: "client_cn",
642+
Groups: []string{"My Org"},
643+
Extra: map[string][]string{
644+
user.CredentialIDKey: {"X509SHA256=dd0a6a295055fa94455c522b0d54ef0499186f454a7cf978b8b346dc35b254f7"},
645+
},
646+
},
647+
},
648+
ExpectErr: false,
636649
},
637650
"ca with multiple organizations": {
638651
Opts: x509.VerifyOptions{
@@ -641,10 +654,17 @@ func TestX509(t *testing.T) {
641654
Certs: getCerts(t, caWithGroups),
642655
User: CommonNameUserConversion,
643656

644-
ExpectUserName: "ROOT CA WITH GROUPS",
645-
ExpectGroups: []string{"My Org", "My Org 1", "My Org 2"},
646-
ExpectOK: true,
647-
ExpectErr: false,
657+
ExpectOK: true,
658+
ExpectResponse: &authenticator.Response{
659+
User: &user.DefaultInfo{
660+
Name: "ROOT CA WITH GROUPS",
661+
Groups: []string{"My Org", "My Org 1", "My Org 2"},
662+
Extra: map[string][]string{
663+
user.CredentialIDKey: {"X509SHA256=6f337bb6576b6f942bd5ac5256f621e352aa7b34d971bda9b8f8981f51bba456"},
664+
},
665+
},
666+
},
667+
ExpectErr: false,
648668
},
649669

650670
"custom conversion error": {
@@ -664,9 +684,13 @@ func TestX509(t *testing.T) {
664684
return &authenticator.Response{User: &user.DefaultInfo{Name: "custom"}}, true, nil
665685
}),
666686

667-
ExpectUserName: "custom",
668-
ExpectOK: true,
669-
ExpectErr: false,
687+
ExpectOK: true,
688+
ExpectResponse: &authenticator.Response{
689+
User: &user.DefaultInfo{
690+
Name: "custom",
691+
},
692+
},
693+
ExpectErr: false,
670694
},
671695

672696
"future cert": {
@@ -697,9 +721,16 @@ func TestX509(t *testing.T) {
697721
Certs: getCertsFromFile(t, "client-valid", "intermediate"),
698722
User: CommonNameUserConversion,
699723

700-
ExpectUserName: "My Client",
701-
ExpectOK: true,
702-
ExpectErr: false,
724+
ExpectOK: true,
725+
ExpectResponse: &authenticator.Response{
726+
User: &user.DefaultInfo{
727+
Name: "My Client",
728+
Extra: map[string][]string{
729+
user.CredentialIDKey: {"X509SHA256=794b0529fd1a72d55d52d98be9bab5b822d16f9ae86c4373fa7beee3cafe8582"},
730+
},
731+
},
732+
},
733+
ExpectErr: false,
703734
},
704735
"multi-level, expired": {
705736
Opts: multilevelOpts,
@@ -712,42 +743,36 @@ func TestX509(t *testing.T) {
712743
}
713744

714745
for k, testCase := range testCases {
715-
req, _ := http.NewRequest("GET", "/", nil)
716-
if !testCase.Insecure {
717-
req.TLS = &tls.ConnectionState{PeerCertificates: testCase.Certs}
718-
}
719-
720-
// this effectively tests the simple dynamic verify function.
721-
a := New(testCase.Opts, testCase.User)
746+
t.Run(k, func(t *testing.T) {
747+
req, _ := http.NewRequest("GET", "/", nil)
748+
if !testCase.Insecure {
749+
req.TLS = &tls.ConnectionState{PeerCertificates: testCase.Certs}
750+
}
722751

723-
resp, ok, err := a.AuthenticateRequest(req)
752+
// this effectively tests the simple dynamic verify function.
753+
a := New(testCase.Opts, testCase.User)
724754

725-
if testCase.ExpectErr && err == nil {
726-
t.Errorf("%s: Expected error, got none", k)
727-
continue
728-
}
729-
if !testCase.ExpectErr && err != nil {
730-
t.Errorf("%s: Got unexpected error: %v", k, err)
731-
continue
732-
}
755+
resp, ok, err := a.AuthenticateRequest(req)
733756

734-
if testCase.ExpectOK != ok {
735-
t.Errorf("%s: Expected ok=%v, got %v", k, testCase.ExpectOK, ok)
736-
continue
737-
}
757+
if testCase.ExpectErr && err == nil {
758+
t.Fatalf("Expected error, got none")
759+
}
760+
if !testCase.ExpectErr && err != nil {
761+
t.Fatalf("Got unexpected error: %v", err)
762+
}
738763

739-
if testCase.ExpectOK {
740-
if testCase.ExpectUserName != resp.User.GetName() {
741-
t.Errorf("%s: Expected user.name=%v, got %v", k, testCase.ExpectUserName, resp.User.GetName())
764+
if testCase.ExpectOK != ok {
765+
t.Fatalf("Expected ok=%v, got %v", testCase.ExpectOK, ok)
742766
}
743767

744-
groups := resp.User.GetGroups()
745-
sort.Strings(testCase.ExpectGroups)
746-
sort.Strings(groups)
747-
if !reflect.DeepEqual(testCase.ExpectGroups, groups) {
748-
t.Errorf("%s: Expected user.groups=%v, got %v", k, testCase.ExpectGroups, groups)
768+
if testCase.ExpectOK {
769+
sort.Strings(testCase.ExpectResponse.User.GetGroups())
770+
sort.Strings(resp.User.GetGroups())
771+
if diff := cmp.Diff(testCase.ExpectResponse, resp); diff != "" {
772+
t.Errorf("Bad response; diff (-want +got)\n%s", diff)
773+
}
749774
}
750-
}
775+
})
751776
}
752777
}
753778

staging/src/k8s.io/apiserver/pkg/authentication/serviceaccount/util.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ const (
3636
ServiceAccountUsernameSeparator = ":"
3737
ServiceAccountGroupPrefix = "system:serviceaccounts:"
3838
AllServiceAccountsGroup = "system:serviceaccounts"
39-
// CredentialIDKey is the key used in a user's "extra" to specify the unique
40-
// identifier for this identity document).
41-
CredentialIDKey = "authentication.kubernetes.io/credential-id"
4239
// IssuedCredentialIDAuditAnnotationKey is the annotation key used in the audit event that is persisted to the
4340
// '/token' endpoint for service accounts.
4441
// This annotation indicates the generated credential identifier for the service account token being issued.
@@ -156,7 +153,7 @@ func (sa *ServiceAccountInfo) UserInfo() user.Info {
156153
if info.Extra == nil {
157154
info.Extra = make(map[string][]string)
158155
}
159-
info.Extra[CredentialIDKey] = []string{sa.CredentialID}
156+
info.Extra[user.CredentialIDKey] = []string{sa.CredentialID}
160157
}
161158
if sa.NodeName != "" {
162159
if info.Extra == nil {

staging/src/k8s.io/apiserver/pkg/authentication/user/user.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ func (i *DefaultInfo) GetExtra() map[string][]string {
6666
return i.Extra
6767
}
6868

69-
// well-known user and group names
7069
const (
70+
// well-known user and group names
7171
SystemPrivilegedGroup = "system:masters"
7272
NodesGroup = "system:nodes"
7373
MonitoringGroup = "system:monitoring"
@@ -81,4 +81,8 @@ const (
8181
KubeProxy = "system:kube-proxy"
8282
KubeControllerManager = "system:kube-controller-manager"
8383
KubeScheduler = "system:kube-scheduler"
84+
85+
// CredentialIDKey is the key used in a user's "extra" to specify the unique
86+
// identifier for this identity document).
87+
CredentialIDKey = "authentication.kubernetes.io/credential-id"
8488
)

test/integration/auth/svcaccttoken_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import (
4141
"k8s.io/apimachinery/pkg/types"
4242
"k8s.io/apiserver/pkg/authentication/authenticator"
4343
apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount"
44+
"k8s.io/apiserver/pkg/authentication/user"
4445
utilfeature "k8s.io/apiserver/pkg/util/feature"
4546
clientset "k8s.io/client-go/kubernetes"
4647
"k8s.io/client-go/kubernetes/scheme"
@@ -237,7 +238,7 @@ func TestServiceAccountTokenCreate(t *testing.T) {
237238
info := doTokenReview(t, cs, treq, false)
238239
// we are not testing the credential-id feature, so delete this value from the returned extra info map
239240
if info.Extra != nil {
240-
delete(info.Extra, apiserverserviceaccount.CredentialIDKey)
241+
delete(info.Extra, user.CredentialIDKey)
241242
}
242243
if len(info.Extra) > 0 {
243244
t.Fatalf("expected Extra to be empty but got: %#v", info.Extra)
@@ -309,7 +310,7 @@ func TestServiceAccountTokenCreate(t *testing.T) {
309310

310311
info := doTokenReview(t, cs, treq, false)
311312
// we are not testing the credential-id feature, so delete this value from the returned extra info map
312-
delete(info.Extra, apiserverserviceaccount.CredentialIDKey)
313+
delete(info.Extra, user.CredentialIDKey)
313314
if len(info.Extra) != 2 {
314315
t.Fatalf("expected Extra have length of 2 but was length %d: %#v", len(info.Extra), info.Extra)
315316
}
@@ -405,7 +406,7 @@ func TestServiceAccountTokenCreate(t *testing.T) {
405406

406407
info := doTokenReview(t, cs, treq, false)
407408
// we are not testing the credential-id feature, so delete this value from the returned extra info map
408-
delete(info.Extra, apiserverserviceaccount.CredentialIDKey)
409+
delete(info.Extra, user.CredentialIDKey)
409410
if len(info.Extra) != len(expectedExtraValues) {
410411
t.Fatalf("expected Extra have length of %d but was length %d: %#v", len(expectedExtraValues), len(info.Extra), info.Extra)
411412
}

0 commit comments

Comments
 (0)