-
Notifications
You must be signed in to change notification settings - Fork 192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
config: allow multiple configurations #164
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,9 +18,12 @@ package proxy | |
|
||
import ( | ||
"bytes" | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"strings" | ||
"sync" | ||
"sync/atomic" | ||
"text/template" | ||
|
||
"github.com/brancz/kube-rbac-proxy/pkg/authn" | ||
|
@@ -35,7 +38,7 @@ import ( | |
// Config holds proxy authorization and authentication settings | ||
type Config struct { | ||
Authentication *authn.AuthnConfig | ||
Authorization *authz.Config | ||
Authorizations []*authz.Config | ||
} | ||
|
||
type kubeRBACProxy struct { | ||
|
@@ -50,7 +53,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 | ||
|
@@ -88,21 +91,38 @@ func (h *kubeRBACProxy) Handle(w http.ResponseWriter, req *http.Request) bool { | |
return false | ||
} | ||
|
||
var wg sync.WaitGroup | ||
var authzDone uint32 | ||
ctx, cancel := context.WithCancel(ctx) | ||
defer cancel() | ||
for _, attrs := range allAttrs { | ||
// Authorize | ||
authorized, reason, err := h.Authorize(ctx, attrs) | ||
if err != nil { | ||
msg := fmt.Sprintf("Authorization error (user=%s, verb=%s, resource=%s, subresource=%s)", u.User.GetName(), attrs.GetVerb(), attrs.GetResource(), attrs.GetSubresource()) | ||
klog.Errorf("%s: %s", msg, err) | ||
http.Error(w, msg, http.StatusInternalServerError) | ||
return false | ||
} | ||
if authorized != authorizer.DecisionAllow { | ||
msg := fmt.Sprintf("Forbidden (user=%s, verb=%s, resource=%s, subresource=%s)", u.User.GetName(), attrs.GetVerb(), attrs.GetResource(), attrs.GetSubresource()) | ||
klog.V(2).Infof("%s. Reason: %q.", msg, reason) | ||
http.Error(w, msg, http.StatusForbidden) | ||
return false | ||
} | ||
// Authorize concurrently, as there can be many outbound requests to the kube API. | ||
wg.Add(1) | ||
go func(attrs authorizer.Attributes) { | ||
defer wg.Done() | ||
authorized, reason, err := h.Authorize(ctx, attrs) | ||
if err != nil { | ||
msg := fmt.Sprintf("Authorization error (user=%s, verb=%s, resource=%s, subresource=%s)", u.User.GetName(), attrs.GetVerb(), attrs.GetResource(), attrs.GetSubresource()) | ||
klog.Errorf("%s: %s", msg, err) | ||
if atomic.CompareAndSwapUint32(&authzDone, 0, 1) { | ||
http.Error(w, msg, http.StatusInternalServerError) | ||
} | ||
cancel() | ||
return | ||
} | ||
if authorized != authorizer.DecisionAllow { | ||
msg := fmt.Sprintf("Forbidden (user=%s, verb=%s, resource=%s, subresource=%s)", u.User.GetName(), attrs.GetVerb(), attrs.GetResource(), attrs.GetSubresource()) | ||
klog.V(2).Infof("%s. Reason: %q.", msg, reason) | ||
if atomic.CompareAndSwapUint32(&authzDone, 0, 1) { | ||
http.Error(w, msg, http.StatusForbidden) | ||
} | ||
cancel() | ||
} | ||
}(attrs) | ||
} | ||
wg.Wait() | ||
if authzDone == 1 { | ||
return false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The solution is pretty effective. And this part is the reason, why it took me ages to make a PR review. I have huge respect for concurrency and the problems it brings. I would've tried to use something well known like a fan out pattern or tried to separate the threading logic from the rest as much as possible. There is a very low level of abstraction in the code base and the hierarchy is pretty flat with lengthy functions, so I won't object as it fits existing code. |
||
} | ||
|
||
if h.Config.Authentication.Header.Enabled { | ||
|
@@ -116,12 +136,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. | ||
|
@@ -148,67 +168,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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe I am lazy, but I would've func (n krpAuthorizerAttributesGetter) GetRequestAttributes(u user.Info, r *http.Request) []authorizer.Attributes {
allAttrs := []authorizer.Attributes{}
for _, ac := range n.authzConfigs{
allAttrs = append(allAttrs, getRequestAttributes(u, r, ac)...)
}
return allAttrs
} |
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a benefit to having the |
||
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) | ||
} | ||
} | ||
|
||
if len(params) == 0 { | ||
continue | ||
} | ||
|
||
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, | ||
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 | ||
} | ||
|
@@ -238,17 +267,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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need this any more. |
||
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, | ||
}, | ||
}) | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would string be more appropriate as it is not a comma-separated list, but it can be specified multiple times? In comparison to
--ignore-paths
below.