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:
Kubernetes Prow Robot 2024-02-05 08:45:43 -08:00 committed by GitHub
commit 8c6e940a97
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 79 additions and 2 deletions

View File

@ -17,17 +17,28 @@ limitations under the License.
package storage
import (
"context"
"testing"
"gopkg.in/square/go-jose.v2/jwt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/audit"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/generic"
genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
"k8s.io/apiserver/pkg/registry/rest"
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"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/registry/registrytest"
token "k8s.io/kubernetes/pkg/serviceaccount"
)
func newStorage(t *testing.T) (*REST, *etcd3testing.EtcdTestServer) {
@ -38,13 +49,35 @@ func newStorage(t *testing.T) (*REST, *etcd3testing.EtcdTestServer) {
DeleteCollectionWorkers: 1,
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 {
t.Fatalf("unexpected error from REST storage: %v", err)
}
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 {
return &api.ServiceAccount{
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) {
storage, server := newStorage(t)
defer server.Terminate(t)

View File

@ -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)},
}
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
}

View File

@ -39,6 +39,12 @@ const (
// CredentialIDKey is the key used in a user's "extra" to specify the unique
// identifier for this identity document).
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
// the authenticating request.
PodNameKey = "authentication.kubernetes.io/pod-name"