Skip to content

Commit

Permalink
config: allow multiple configurations
Browse files Browse the repository at this point in the history
This commit fixes issue #154 by enabling the user to repeat the
`--config-file` flag multiple times to specify multiple configurations.
Doing so, allows the user to declare that the krp should, e.g., enforce
multiple resource attributes.

Signed-off-by: Lucas Servén Marín <[email protected]>
  • Loading branch information
squat committed Apr 1, 2022
1 parent 9f21af2 commit e6e5cc9
Show file tree
Hide file tree
Showing 14 changed files with 449 additions and 103 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Usage of _output/kube-rbac-proxy:
--auth-header-user-field-name string The name of the field inside a http(2) request header to tell the upstream server about the user's name (default "x-remote-user")
--auth-token-audiences strings Comma-separated list of token audiences to accept. By default a token does not have to have any specific audience. It is recommended to set a specific audience.
--client-ca-file string If set, any request presenting a client certificate signed by one of the authorities in the client-ca-file is authenticated with an identity corresponding to the CommonName of the client certificate.
--config-file string Configuration file to configure kube-rbac-proxy.
--config-file strings Configuration file to configure kube-rbac-proxy. Can be specified multiple times to configure multiple resource authorizations.
--ignore-paths strings Comma-separated list of paths against which kube-rbac-proxy will proxy without performing an authentication or authorization check. Cannot be used with --allow-paths.
--insecure-listen-address string The address the kube-rbac-proxy HTTP server should listen on.
--kubeconfig string Path to a kubeconfig file, specifying how to connect to the API server. If unset, in-cluster configuration will be used
Expand Down
37 changes: 21 additions & 16 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,10 @@ func main() {
OIDC: &authn.OIDCConfig{},
Token: &authn.TokenConfig{},
},
Authorization: &authz.Config{},
Authorizations: []*authz.Config{},
},
}
configFileName := ""
var configFileNames []string

// Add klog flags
klogFlags := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
Expand All @@ -104,7 +104,7 @@ func main() {
flagset.StringVar(&cfg.upstream, "upstream", "", "The upstream URL to proxy to once requests have successfully been authenticated and authorized.")
flagset.BoolVar(&cfg.upstreamForceH2C, "upstream-force-h2c", false, "Force h2c to communiate with the upstream. This is required when the upstream speaks h2c(http/2 cleartext - insecure variant of http/2) only. For example, go-grpc server in the insecure mode, such as helm's tiller w/o TLS, speaks h2c only")
flagset.StringVar(&cfg.upstreamCAFile, "upstream-ca-file", "", "The CA the upstream uses for TLS connection. This is required when the upstream uses TLS and its own CA certificate")
flagset.StringVar(&configFileName, "config-file", "", "Configuration file to configure kube-rbac-proxy.")
flagset.StringSliceVar(&configFileNames, "config-file", []string{}, "Configuration file to configure kube-rbac-proxy. Can be specified multiple times to configure multiple resource authorizations.")
flagset.StringSliceVar(&cfg.allowPaths, "allow-paths", nil, "Comma-separated list of paths against which kube-rbac-proxy matches the incoming request. If the request doesn't match, kube-rbac-proxy responds with a 404 status code. If omitted, the incoming request path isn't checked. Cannot be used with --ignore-paths.")
flagset.StringSliceVar(&cfg.ignorePaths, "ignore-paths", nil, "Comma-separated list of paths against which kube-rbac-proxy will proxy without performing an authentication or authorization check. Cannot be used with --allow-paths.")

Expand Down Expand Up @@ -146,21 +146,22 @@ func main() {
klog.Fatalf("Failed to parse upstream URL: %v", err)
}

if configFileName != "" {
klog.Infof("Reading config file: %s", configFileName)
b, err := ioutil.ReadFile(configFileName)
if err != nil {
klog.Fatalf("Failed to read resource-attribute file: %v", err)
}
if len(configFileNames) != 0 {
for _, cfn := range configFileNames {
klog.Infof("Reading config file: %s", cfn)
b, err := ioutil.ReadFile(cfn)
if err != nil {
klog.Fatalf("Failed to read resource-attribute file: %v", err)
}

configfile := configfile{}
configfile := configfile{}

err = yaml.Unmarshal(b, &configfile)
if err != nil {
klog.Fatalf("Failed to parse config file content: %v", err)
}
if err := yaml.Unmarshal(b, &configfile); err != nil {
klog.Fatalf("Failed to parse config file content: %v", err)
}

cfg.auth.Authorization = configfile.AuthorizationConfig
cfg.auth.Authorizations = append(cfg.auth.Authorizations, configfile.AuthorizationConfig)
}
}

kubeClient, err := kubernetes.NewForConfig(kcfg)
Expand Down Expand Up @@ -202,7 +203,11 @@ func main() {
klog.Fatalf("Failed to create sar authorizer: %v", err)
}

staticAuthorizer, err := authz.NewStaticAuthorizer(cfg.auth.Authorization.Static)
var sacs []authz.StaticAuthorizationConfig
for _, ac := range cfg.auth.Authorizations {
sacs = append(sacs, ac.Static...)
}
staticAuthorizer, err := authz.NewStaticAuthorizer(sacs)
if err != nil {
klog.Fatalf("Failed to create static authorizer: %v", err)
}
Expand Down
145 changes: 78 additions & 67 deletions pkg/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import (
// Config holds proxy authorization and authentication settings
type Config struct {
Authentication *authn.AuthnConfig
Authorization *authz.Config
Authorizations []*authz.Config
}

type kubeRBACProxy struct {
Expand All @@ -50,7 +50,7 @@ type kubeRBACProxy struct {
}

func new(authenticator authenticator.Request, authorizer authorizer.Authorizer, config Config) *kubeRBACProxy {
return &kubeRBACProxy{authenticator, authorizer, newKubeRBACProxyAuthorizerAttributesGetter(config.Authorization), config}
return &kubeRBACProxy{authenticator, authorizer, newKubeRBACProxyAuthorizerAttributesGetter(config.Authorizations), config}
}

// New creates an authenticator, an authorizer, and a matching authorizer attributes getter compatible with the kube-rbac-proxy
Expand Down Expand Up @@ -116,12 +116,12 @@ func (h *kubeRBACProxy) Handle(w http.ResponseWriter, req *http.Request) bool {
return true
}

func newKubeRBACProxyAuthorizerAttributesGetter(authzConfig *authz.Config) *krpAuthorizerAttributesGetter {
return &krpAuthorizerAttributesGetter{authzConfig}
func newKubeRBACProxyAuthorizerAttributesGetter(authzConfigs []*authz.Config) *krpAuthorizerAttributesGetter {
return &krpAuthorizerAttributesGetter{authzConfigs}
}

type krpAuthorizerAttributesGetter struct {
authzConfig *authz.Config
authzConfigs []*authz.Config
}

// GetRequestAttributes populates authorizer attributes for the requests to kube-rbac-proxy.
Expand All @@ -148,67 +148,76 @@ func (n krpAuthorizerAttributesGetter) GetRequestAttributes(u user.Info, r *http
}
}()

if n.authzConfig.ResourceAttributes == nil {
// Default attributes mirror the API attributes that would allow this access to kube-rbac-proxy
allAttrs := append(allAttrs, authorizer.AttributesRecord{
User: u,
Verb: apiVerb,
Namespace: "",
APIGroup: "",
APIVersion: "",
Resource: "",
Subresource: "",
Name: "",
ResourceRequest: false,
Path: r.URL.Path,
})
return allAttrs
// Default attributes mirror the API attributes that would allow this access to kube-rbac-proxy
defaultAttributesRecord := authorizer.AttributesRecord{
User: u,
Verb: apiVerb,
Namespace: "",
APIGroup: "",
APIVersion: "",
Resource: "",
Subresource: "",
Name: "",
ResourceRequest: false,
Path: r.URL.Path,
}

if n.authzConfig.Rewrites == nil {
allAttrs := append(allAttrs, authorizer.AttributesRecord{
User: u,
Verb: apiVerb,
Namespace: n.authzConfig.ResourceAttributes.Namespace,
APIGroup: n.authzConfig.ResourceAttributes.APIGroup,
APIVersion: n.authzConfig.ResourceAttributes.APIVersion,
Resource: n.authzConfig.ResourceAttributes.Resource,
Subresource: n.authzConfig.ResourceAttributes.Subresource,
Name: n.authzConfig.ResourceAttributes.Name,
ResourceRequest: true,
})
if len(n.authzConfigs) == 0 {
allAttrs = append(allAttrs, defaultAttributesRecord)
return allAttrs
}

params := []string{}
if n.authzConfig.Rewrites.ByQueryParameter != nil && n.authzConfig.Rewrites.ByQueryParameter.Name != "" {
if ps, ok := r.URL.Query()[n.authzConfig.Rewrites.ByQueryParameter.Name]; ok {
params = append(params, ps...)
for _, ac := range n.authzConfigs {
if ac.ResourceAttributes == nil {
allAttrs = append(allAttrs, defaultAttributesRecord)
continue
}
}
if n.authzConfig.Rewrites.ByHTTPHeader != nil && n.authzConfig.Rewrites.ByHTTPHeader.Name != "" {
if p := r.Header.Get(n.authzConfig.Rewrites.ByHTTPHeader.Name); p != "" {
params = append(params, p)

if ac.Rewrites == nil {
allAttrs = append(allAttrs, authorizer.AttributesRecord{
User: u,
Verb: apiVerb,
Namespace: ac.ResourceAttributes.Namespace,
APIGroup: ac.ResourceAttributes.APIGroup,
APIVersion: ac.ResourceAttributes.APIVersion,
Resource: ac.ResourceAttributes.Resource,
Subresource: ac.ResourceAttributes.Subresource,
Name: ac.ResourceAttributes.Name,
ResourceRequest: true,
})
continue
}
}

if len(params) == 0 {
return allAttrs
}
params := []string{}
if ac.Rewrites.ByQueryParameter != nil && ac.Rewrites.ByQueryParameter.Name != "" {
if ps, ok := r.URL.Query()[ac.Rewrites.ByQueryParameter.Name]; ok {
params = append(params, ps...)
}
}
if ac.Rewrites.ByHTTPHeader != nil && ac.Rewrites.ByHTTPHeader.Name != "" {
if p := r.Header.Get(ac.Rewrites.ByHTTPHeader.Name); p != "" {
params = append(params, p)
}
}

for _, param := range params {
attrs := authorizer.AttributesRecord{
User: u,
Verb: apiVerb,
Namespace: templateWithValue(n.authzConfig.ResourceAttributes.Namespace, param),
APIGroup: templateWithValue(n.authzConfig.ResourceAttributes.APIGroup, param),
APIVersion: templateWithValue(n.authzConfig.ResourceAttributes.APIVersion, param),
Resource: templateWithValue(n.authzConfig.ResourceAttributes.Resource, param),
Subresource: templateWithValue(n.authzConfig.ResourceAttributes.Subresource, param),
Name: templateWithValue(n.authzConfig.ResourceAttributes.Name, param),
ResourceRequest: true,
if len(params) == 0 {
continue
}

for _, param := range params {
attrs := authorizer.AttributesRecord{
User: u,
Verb: apiVerb,
Namespace: templateWithValue(ac.ResourceAttributes.Namespace, param),
APIGroup: templateWithValue(ac.ResourceAttributes.APIGroup, param),
APIVersion: templateWithValue(ac.ResourceAttributes.APIVersion, param),
Resource: templateWithValue(ac.ResourceAttributes.Resource, param),
Subresource: templateWithValue(ac.ResourceAttributes.Subresource, param),
Name: templateWithValue(ac.ResourceAttributes.Name, param),
ResourceRequest: true,
}
allAttrs = append(allAttrs, attrs)
}
allAttrs = append(allAttrs, attrs)
}
return allAttrs
}
Expand Down Expand Up @@ -238,17 +247,19 @@ func (c *Config) DeepCopy() *Config {
}
}

if c.Authorization != nil {
if c.Authorization.ResourceAttributes != nil {
res.Authorization = &authz.Config{
ResourceAttributes: &authz.ResourceAttributes{
Namespace: c.Authorization.ResourceAttributes.Namespace,
APIGroup: c.Authorization.ResourceAttributes.APIGroup,
APIVersion: c.Authorization.ResourceAttributes.APIVersion,
Resource: c.Authorization.ResourceAttributes.Resource,
Subresource: c.Authorization.ResourceAttributes.Subresource,
Name: c.Authorization.ResourceAttributes.Name,
},
if len(c.Authorizations) != 0 {
for _, a := range c.Authorizations {
if a != nil && a.ResourceAttributes != nil {
res.Authorizations = append(res.Authorizations, &authz.Config{
ResourceAttributes: &authz.ResourceAttributes{
Namespace: a.ResourceAttributes.Namespace,
APIGroup: a.ResourceAttributes.APIGroup,
APIVersion: a.ResourceAttributes.APIVersion,
Resource: a.ResourceAttributes.Resource,
Subresource: a.ResourceAttributes.Subresource,
Name: a.ResourceAttributes.Name,
},
})
}
}
}
Expand Down
Loading

0 comments on commit e6e5cc9

Please sign in to comment.