@@ -17,6 +17,8 @@ limitations under the License.
1717package options
1818
1919import (
20+ "context"
21+
2022 kcpkubernetesinformers "github.com/kcp-dev/client-go/informers"
2123 "github.com/spf13/pflag"
2224
@@ -25,7 +27,11 @@ import (
2527 "k8s.io/apiserver/pkg/authorization/authorizerfactory"
2628 "k8s.io/apiserver/pkg/authorization/path"
2729 "k8s.io/apiserver/pkg/authorization/union"
30+ "k8s.io/apiserver/pkg/informerfactoryhack"
2831 genericapiserver "k8s.io/apiserver/pkg/server"
32+ "k8s.io/apiserver/pkg/server/egressselector"
33+ authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
34+ kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options"
2935
3036 authz "github.com/kcp-dev/kcp/pkg/authorization"
3137 kcpinformers "github.com/kcp-dev/kcp/sdk/client/informers/externalversions"
@@ -38,6 +44,10 @@ type Authorization struct {
3844
3945 // AlwaysAllowGroups are groups which are allowed to take any actions. In kube, this is privileged system group.
4046 AlwaysAllowGroups []string
47+
48+ // Webhook contains flags to enable an external HTTPS webhook to perform
49+ // authorization against. Note that not all built-in options are supported by kcp.
50+ Webhook * kubeoptions.BuiltInAuthorizationOptions
4151}
4252
4353func NewAuthorization () * Authorization {
@@ -46,6 +56,7 @@ func NewAuthorization() *Authorization {
4656 // This field can be cleared by callers if they don't want this behavior.
4757 AlwaysAllowPaths : []string {"/healthz" , "/readyz" , "/livez" },
4858 AlwaysAllowGroups : []string {user .SystemPrivilegedGroup },
59+ Webhook : kubeoptions .NewBuiltInAuthorizationOptions (),
4960 }
5061}
5162
@@ -61,13 +72,35 @@ func (s *Authorization) WithAlwaysAllowPaths(paths ...string) *Authorization {
6172 return s
6273}
6374
75+ func (s * Authorization ) Complete () error {
76+ if s == nil {
77+ return nil
78+ }
79+
80+ // kcp only supports optionally specifying an external authorization webhook
81+ // in addition to the built-in authorization logic.
82+ if s .Webhook .WebhookConfigFile != "" {
83+ s .Webhook .Modes = []string {authzmodes .ModeWebhook }
84+ } else {
85+ s .Webhook = nil
86+ }
87+
88+ return nil
89+ }
90+
6491func (s * Authorization ) Validate () []error {
6592 if s == nil {
6693 return nil
6794 }
6895
6996 allErrors := []error {}
7097
98+ if s .Webhook != nil {
99+ if errs := s .Webhook .Validate (); len (errs ) > 0 {
100+ allErrors = append (allErrors , errs ... )
101+ }
102+ }
103+
71104 return allErrors
72105}
73106
@@ -79,11 +112,48 @@ func (s *Authorization) AddFlags(fs *pflag.FlagSet) {
79112 fs .StringSliceVar (& s .AlwaysAllowPaths , "authorization-always-allow-paths" , s .AlwaysAllowPaths ,
80113 "A list of HTTP paths to skip during authorization, i.e. these are authorized without " +
81114 "contacting the 'core' kubernetes server." )
115+
116+ // Only surface selected, webhook-related CLI flags
117+
118+ fs .StringVar (& s .Webhook .WebhookConfigFile , "authorization-webhook-config-file" , s .Webhook .WebhookConfigFile ,
119+ "File with optional webhook configuration in kubeconfig format. The API server will query the remote service to determine access on the API server's secure port." )
120+
121+ fs .StringVar (& s .Webhook .WebhookVersion , "authorization-webhook-version" , s .Webhook .WebhookVersion ,
122+ "The API version of the authorization.k8s.io SubjectAccessReview to send to and expect from the webhook." )
123+
124+ fs .DurationVar (& s .Webhook .WebhookCacheAuthorizedTTL , "authorization-webhook-cache-authorized-ttl" , s .Webhook .WebhookCacheAuthorizedTTL ,
125+ "The duration to cache 'authorized' responses from the webhook authorizer." )
126+
127+ fs .DurationVar (& s .Webhook .WebhookCacheUnauthorizedTTL , "authorization-webhook-cache-unauthorized-ttl" , s .Webhook .WebhookCacheUnauthorizedTTL ,
128+ "The duration to cache 'unauthorized' responses from the webhook authorizer." )
82129}
83130
84- func (s * Authorization ) ApplyTo (config * genericapiserver.Config , kubeInformers , globalKubeInformers kcpkubernetesinformers.SharedInformerFactory , kcpInformers , globalKcpInformers kcpinformers.SharedInformerFactory ) error {
131+ func (s * Authorization ) ApplyTo (ctx context. Context , config * genericapiserver.Config , kubeInformers , globalKubeInformers kcpkubernetesinformers.SharedInformerFactory , kcpInformers , globalKcpInformers kcpinformers.SharedInformerFactory ) error {
85132 var authorizers []authorizer.Authorizer
86133
134+ // re-use the authorizer from the generic control plane (this is only set for webhooks)
135+ if webhook := s .Webhook ; webhook != nil && webhook .WebhookConfigFile != "" {
136+ authorizationConfig , err := webhook .ToAuthorizationConfig (informerfactoryhack .Wrap (kubeInformers ))
137+ if err != nil {
138+ return err
139+ }
140+
141+ if config .EgressSelector != nil {
142+ egressDialer , err := config .EgressSelector .Lookup (egressselector .ControlPlane .AsNetworkContext ())
143+ if err != nil {
144+ return err
145+ }
146+ authorizationConfig .CustomDial = egressDialer
147+ }
148+
149+ authorizer , _ , err := authorizationConfig .New (ctx , config .APIServerID )
150+ if err != nil {
151+ return err
152+ }
153+
154+ authorizers = append (authorizers , authorizer )
155+ }
156+
87157 localLogicalClusterLister := kcpInformers .Core ().V1alpha1 ().LogicalClusters ().Lister ()
88158 globalLogicalClusterLister := globalKcpInformers .Core ().V1alpha1 ().LogicalClusters ().Lister ()
89159
0 commit comments