mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 15:25:57 +00:00
use authentication.kubernetes.io/issued-credential-id audit annotation in serviceaccount token registry endpoint
This commit is contained in:
parent
a8f6ea2420
commit
7f12735fff
@ -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"
|
||||||
|
Loading…
Reference in New Issue
Block a user