Skip to content

Commit

Permalink
Merge 6f6e951 into d28fc04
Browse files Browse the repository at this point in the history
  • Loading branch information
patelpayal committed Jun 1, 2018
2 parents d28fc04 + 6f6e951 commit 58a8151
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 19 deletions.
14 changes: 10 additions & 4 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"os"
"time"

"crypto/tls"

authn "k8s.io/api/authentication/v1beta1"
authz "k8s.io/api/authorization/v1beta1"
)
Expand Down Expand Up @@ -53,6 +55,8 @@ func GetLogger(ctx context.Context) Logger {
// IdentityToken provides an ntoken for Athenz access for the authorization handler itself.
type IdentityToken func() (string, error)

type IdentityAthenzX509 func() (*tls.Config, error)

// AthenzPrincipal represents a valid Athenz principal.
type AthenzPrincipal struct {
Domain string // Athenz domain
Expand Down Expand Up @@ -116,10 +120,12 @@ type AuthenticationConfig struct {

// AuthorizationConfig is the authorization configuration
type AuthorizationConfig struct {
Config // the base config
HelpMessage string // additional message for the user on internal authz errors
Token IdentityToken // the token provider for calls to Athenz
Mapper ResourceMapper // the resource mapper
Config // the base config
HelpMessage string // additional message for the user on internal authz errors
Token IdentityToken // the token provider for calls to Athenz
AthenzX509 IdentityAthenzX509 // the x509 provider for calls to Athenz
AthenzClientAuthnx509Mode bool // enable/disable x509 mode for Identity athenz x509
Mapper ResourceMapper // the resource mapper
}

// NewAuthenticator returns a handler that can service an authentication request.
Expand Down
29 changes: 28 additions & 1 deletion authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,28 @@ func (a *authorizer) client(ctx context.Context) (*client, error) {
return newClient(a.Endpoint, a.Timeout, xp), nil
}

// clientX509 returns the client set up with x509 cert and key to make calls to Athenz.
func (a *authorizer) clientX509(ctx context.Context) (*client, error) {

config, err := a.AthenzX509()
if err != nil {
return nil, err
}
xpX509 := &http.Transport{
TLSClientConfig: config,
}

debugXp := &debugTransport{}
if isLogEnabled(ctx, LogTraceAthenz) {
debugXp = &debugTransport{
log: getLogger(ctx),
RoundTripper: xpX509,
}
return newClient(a.Endpoint, a.Timeout, debugXp), nil
}
return newClient(a.Endpoint, a.Timeout, xpX509), nil
}

// getSubjectAccessReview extracts the subject access review object from the request and returns it.
func (a *authorizer) getSubjectAccessReview(ctx context.Context, req *http.Request) (*authz.SubjectAccessReview, error) {
b, err := ioutil.ReadAll(req.Body)
Expand Down Expand Up @@ -131,8 +153,13 @@ func (a *authorizer) authorize(ctx context.Context, sr authz.SubjectAccessReview
}
internal := "internal setup error."
var via string
var client *client
for _, check := range checks {
client, err := a.client(ctx)
if a.AthenzClientAuthnx509Mode {
client, err = a.clientX509(ctx)
} else {
client, err = a.client(ctx)
}
if err != nil {
return deny(NewAuthzError(err, internal), true)
}
Expand Down
100 changes: 86 additions & 14 deletions authz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,16 @@ package webhook
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"

"encoding/json"

"fmt"

authz "k8s.io/api/authorization/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
Expand Down Expand Up @@ -83,6 +82,35 @@ func newAuthzScaffold(t *testing.T) *authzScaffold {
}
}

func newAuthzScaffoldX509(t *testing.T) *authzScaffold {
m := newMockZMS()
l, p := logProvider()
c := AuthorizationConfig{
Config: Config{
AuthHeader: "X-Auth",
Endpoint: m.URL,
Timeout: 200 * time.Millisecond,
LogProvider: p,
},
HelpMessage: helpText,
AthenzX509: func() (*tls.Config, error) {
return &tls.Config{}, nil
},
AthenzClientAuthnx509Mode: true,
Mapper: mrfn(func(ctx context.Context, spec authz.SubjectAccessReviewSpec) (principal string, checks []AthenzAccessCheck, err error) {
return "std.principal",
[]AthenzAccessCheck{{Action: "frob-athenz", Resource: "my.domain:knob"}},
nil
}),
}
return &authzScaffold{
t: t,
mockZMS: m,
l: l,
config: c,
}
}

func stdAuthzInput() authz.SubjectAccessReview {
return authz.SubjectAccessReview{
TypeMeta: metav1.TypeMeta{
Expand Down Expand Up @@ -169,31 +197,75 @@ func TestAuthzHappyPath(t *testing.T) {
s.containsLog("authz granted bob: get on foo-bar:baz:: -> via frob-athenz on my.domain:knob")
}

func TestAuthzZMSReject(t *testing.T) {
s := newAuthzScaffold(t)
func TestAuthzHappyPathX509(t *testing.T) {
s := newAuthzScaffoldX509(t)
s.config.LogFlags = LogTraceAthenz | LogTraceServer
defer s.Close()
var urlPath string
grant := struct {
Granted bool `json:"granted"`
}{false}
}{true}
zmsHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
urlPath = r.URL.Path
writeJSON(testContext, w, grant)
})
input := stdAuthzInput()
ar := runAuthzTest(s, serialize(input), zmsHandler)
w := ar.w
body := ar.body

if w.Result().StatusCode != 200 {
t.Fatal("invalid status code", w.Result().StatusCode)
}
tr := checkGrant(t, body.Bytes(), false)
if tr.Status.EvaluationError == "" {
t.Error("eval error not set")
if urlPath != "/access/frob-athenz/my.domain:knob" {
t.Error("invalid ZMS URL path", urlPath)
}
if tr.Status.Reason != "" {
t.Error("authz internals leak")
tr := checkGrant(t, body.Bytes(), true)
if tr.Kind != input.Kind {
t.Error("invalid Kind", tr.Kind)
}
if tr.APIVersion != input.APIVersion {
t.Error("invalid API version", tr.APIVersion)
}
s.containsLog("authz granted bob: get on foo-bar:baz:: -> via frob-athenz on my.domain:knob")
}

func TestAuthzZMSReject(t *testing.T) {

tests := []struct {
name string
s interface{}
}{
{"token", newAuthnScaffold(t)},
{"x509", newAuthzScaffoldX509(t)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := newAuthzScaffold(t)
defer s.Close()
grant := struct {
Granted bool `json:"granted"`
}{false}
zmsHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
writeJSON(testContext, w, grant)
})
input := stdAuthzInput()
ar := runAuthzTest(s, serialize(input), zmsHandler)
w := ar.w
body := ar.body

if w.Result().StatusCode != 200 {
t.Fatal("invalid status code", w.Result().StatusCode)
}
tr := checkGrant(t, body.Bytes(), false)
if tr.Status.EvaluationError == "" {
t.Error("eval error not set")
}
if tr.Status.Reason != "" {
t.Error("authz internals leak")
}
s.containsLog("authz denied bob: get on foo-bar:baz:: -> error:principal std.principal does not have access to any of 'frob-athenz on my.domain:knob' resources")
})
}
s.containsLog("authz denied bob: get on foo-bar:baz:: -> error:principal std.principal does not have access to any of 'frob-athenz on my.domain:knob' resources")
}

func TestAuthzMapperBypass(t *testing.T) {
Expand Down

0 comments on commit 58a8151

Please sign in to comment.