Merge pull request #123098 from munnerz/4193-jti-audit-changes
use authentication.kubernetes.io/issued-credential-id audit annotation in serviceaccount token registry endpoint
This commit is contained in:
		@@ -17,17 +17,28 @@ limitations under the License.
 | 
				
			|||||||
package storage
 | 
					package storage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gopkg.in/square/go-jose.v2/jwt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/fields"
 | 
						"k8s.io/apimachinery/pkg/fields"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/labels"
 | 
						"k8s.io/apimachinery/pkg/labels"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
						"k8s.io/apimachinery/pkg/runtime"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/audit"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/endpoints/request"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/registry/generic"
 | 
						"k8s.io/apiserver/pkg/registry/generic"
 | 
				
			||||||
	genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
 | 
						genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/registry/rest"
 | 
				
			||||||
	etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
 | 
						etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
 | 
				
			||||||
 | 
						utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
				
			||||||
 | 
						featuregatetesting "k8s.io/component-base/featuregate/testing"
 | 
				
			||||||
 | 
						authenticationapi "k8s.io/kubernetes/pkg/apis/authentication"
 | 
				
			||||||
	api "k8s.io/kubernetes/pkg/apis/core"
 | 
						api "k8s.io/kubernetes/pkg/apis/core"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/features"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/registry/registrytest"
 | 
						"k8s.io/kubernetes/pkg/registry/registrytest"
 | 
				
			||||||
 | 
						token "k8s.io/kubernetes/pkg/serviceaccount"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func newStorage(t *testing.T) (*REST, *etcd3testing.EtcdTestServer) {
 | 
					func newStorage(t *testing.T) (*REST, *etcd3testing.EtcdTestServer) {
 | 
				
			||||||
@@ -38,13 +49,35 @@ func newStorage(t *testing.T) (*REST, *etcd3testing.EtcdTestServer) {
 | 
				
			|||||||
		DeleteCollectionWorkers: 1,
 | 
							DeleteCollectionWorkers: 1,
 | 
				
			||||||
		ResourcePrefix:          "serviceaccounts",
 | 
							ResourcePrefix:          "serviceaccounts",
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	rest, err := NewREST(restOptions, nil, nil, 0, nil, nil, nil, false)
 | 
						// set issuer, podStore and secretStore to allow the token endpoint to be initialised
 | 
				
			||||||
 | 
						rest, err := NewREST(restOptions, fakeTokenGenerator{"fake"}, nil, 0, panicGetter{}, panicGetter{}, nil, false)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatalf("unexpected error from REST storage: %v", err)
 | 
							t.Fatalf("unexpected error from REST storage: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return rest, server
 | 
						return rest, server
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// A basic fake token generator which always returns a static string
 | 
				
			||||||
 | 
					type fakeTokenGenerator struct {
 | 
				
			||||||
 | 
						staticToken string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (f fakeTokenGenerator) GenerateToken(claims *jwt.Claims, privateClaims interface{}) (string, error) {
 | 
				
			||||||
 | 
						return f.staticToken, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ token.TokenGenerator = fakeTokenGenerator{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Currently this getter only panics as the only test case doesn't actually need the getters to function.
 | 
				
			||||||
 | 
					// When more test cases are added, this getter will need extending/replacing to have a real test implementation.
 | 
				
			||||||
 | 
					type panicGetter struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (f panicGetter) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
 | 
				
			||||||
 | 
						panic("not implemented")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ rest.Getter = panicGetter{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func validNewServiceAccount(name string) *api.ServiceAccount {
 | 
					func validNewServiceAccount(name string) *api.ServiceAccount {
 | 
				
			||||||
	return &api.ServiceAccount{
 | 
						return &api.ServiceAccount{
 | 
				
			||||||
		ObjectMeta: metav1.ObjectMeta{
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
@@ -73,6 +106,44 @@ func TestCreate(t *testing.T) {
 | 
				
			|||||||
	)
 | 
						)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCreate_Token_SetsCredentialIDAuditAnnotation(t *testing.T) {
 | 
				
			||||||
 | 
						storage, server := newStorage(t)
 | 
				
			||||||
 | 
						defer server.Terminate(t)
 | 
				
			||||||
 | 
						defer storage.Store.DestroyFunc()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Enable JTI feature
 | 
				
			||||||
 | 
						defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenJTI, true)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx := context.Background()
 | 
				
			||||||
 | 
						// Create a test service account
 | 
				
			||||||
 | 
						serviceAccount := validNewServiceAccount("foo")
 | 
				
			||||||
 | 
						// add the namespace to the context as it is required
 | 
				
			||||||
 | 
						ctx = request.WithNamespace(ctx, serviceAccount.Namespace)
 | 
				
			||||||
 | 
						_, err := storage.Store.Create(ctx, serviceAccount, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("failed creating test service account: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// create an audit context to allow recording audit information
 | 
				
			||||||
 | 
						ctx = audit.WithAuditContext(ctx)
 | 
				
			||||||
 | 
						_, err = storage.Token.Create(ctx, serviceAccount.Name, &authenticationapi.TokenRequest{
 | 
				
			||||||
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
								Name:      serviceAccount.Name,
 | 
				
			||||||
 | 
								Namespace: serviceAccount.Namespace,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Spec: authenticationapi.TokenRequestSpec{ExpirationSeconds: 3600},
 | 
				
			||||||
 | 
						}, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("failed calling /token endpoint for service account: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auditContext := audit.AuditContextFrom(ctx)
 | 
				
			||||||
 | 
						issuedCredentialID, ok := auditContext.Event.Annotations["authentication.kubernetes.io/issued-credential-id"]
 | 
				
			||||||
 | 
						if !ok || len(issuedCredentialID) == 0 {
 | 
				
			||||||
 | 
							t.Errorf("did not find issued-credential-id in audit event annotations")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestUpdate(t *testing.T) {
 | 
					func TestUpdate(t *testing.T) {
 | 
				
			||||||
	storage, server := newStorage(t)
 | 
						storage, server := newStorage(t)
 | 
				
			||||||
	defer server.Terminate(t)
 | 
						defer server.Terminate(t)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -235,7 +235,7 @@ func (r *TokenREST) Create(ctx context.Context, name string, obj runtime.Object,
 | 
				
			|||||||
		ExpirationTimestamp: metav1.Time{Time: nowTime.Add(time.Duration(out.Spec.ExpirationSeconds) * time.Second)},
 | 
							ExpirationTimestamp: metav1.Time{Time: nowTime.Add(time.Duration(out.Spec.ExpirationSeconds) * time.Second)},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenJTI) && len(sc.ID) > 0 {
 | 
						if utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenJTI) && len(sc.ID) > 0 {
 | 
				
			||||||
		audit.AddAuditAnnotation(ctx, serviceaccount.CredentialIDKey, serviceaccount.CredentialIDForJTI(sc.ID))
 | 
							audit.AddAuditAnnotation(ctx, serviceaccount.IssuedCredentialIDAuditAnnotationKey, serviceaccount.CredentialIDForJTI(sc.ID))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return out, nil
 | 
						return out, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,6 +39,12 @@ const (
 | 
				
			|||||||
	// CredentialIDKey is the key used in a user's "extra" to specify the unique
 | 
						// CredentialIDKey is the key used in a user's "extra" to specify the unique
 | 
				
			||||||
	// identifier for this identity document).
 | 
						// identifier for this identity document).
 | 
				
			||||||
	CredentialIDKey = "authentication.kubernetes.io/credential-id"
 | 
						CredentialIDKey = "authentication.kubernetes.io/credential-id"
 | 
				
			||||||
 | 
						// IssuedCredentialIDAuditAnnotationKey is the annotation key used in the audit event that is persisted to the
 | 
				
			||||||
 | 
						// '/token' endpoint for service accounts.
 | 
				
			||||||
 | 
						// This annotation indicates the generated credential identifier for the service account token being issued.
 | 
				
			||||||
 | 
						// This is useful when tracing back the origin of tokens that have gone on to make request that have persisted
 | 
				
			||||||
 | 
						// their credential-identifier into the audit log via the user's extra info stored on subsequent audit events.
 | 
				
			||||||
 | 
						IssuedCredentialIDAuditAnnotationKey = "authentication.kubernetes.io/issued-credential-id"
 | 
				
			||||||
	// PodNameKey is the key used in a user's "extra" to specify the pod name of
 | 
						// PodNameKey is the key used in a user's "extra" to specify the pod name of
 | 
				
			||||||
	// the authenticating request.
 | 
						// the authenticating request.
 | 
				
			||||||
	PodNameKey = "authentication.kubernetes.io/pod-name"
 | 
						PodNameKey = "authentication.kubernetes.io/pod-name"
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user