Skip to content

Commit

Permalink
Adding Authz support for Azure (#5)
Browse files Browse the repository at this point in the history
Adding Authz support for Azure
  • Loading branch information
krdhruva authored May 19, 2020
1 parent 295a189 commit 60285b3
Show file tree
Hide file tree
Showing 64 changed files with 4,655 additions and 189 deletions.
26 changes: 15 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
name: CI

on:
pull_request:
branches:
- '*'
push:
branches-ignore:
- 'release-*'
tags-ignore:
- '*.*'
branches:
- master

jobs:

Expand All @@ -14,28 +15,31 @@ jobs:
runs-on: ubuntu-latest
steps:

- name: Set up Go 1.13
uses: actions/setup-go@v1
- name: Set up Go 1.14
uses: actions/setup-go@v2
with:
go-version: 1.13
go-version: 1.14
id: go

- name: Check out code into the Go module directory
uses: actions/checkout@v1
- uses: actions/checkout@v2

- name: Set up Docker Buildx
id: buildx
uses: crazy-max/ghaction-docker-buildx@v1
with:
version: latest
buildx-version: latest
qemu-version: latest

- name: Available platforms
run: echo ${{ steps.buildx.outputs.platforms }}

- name: Run checks
- name: Prepare Host
run: |
sudo apt-get -qq update || true
sudo apt-get install -y bzr
- name: Run checks
run: |
make ci
- name: Build
Expand Down
20 changes: 17 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:

- name: Check out code into the Go module directory
uses: actions/checkout@v1
- uses: actions/checkout@v1

- name: Print version info
id: semver
Expand All @@ -24,7 +23,8 @@ jobs:
id: buildx
uses: crazy-max/ghaction-docker-buildx@v1
with:
version: latest
buildx-version: latest
qemu-version: latest

- name: Available platforms
run: echo ${{ steps.buildx.outputs.platforms }}
Expand All @@ -38,6 +38,20 @@ jobs:
docker login --username ${USERNAME} --password ${DOCKER_TOKEN}
make release
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
draft: true
files: |
bin/guard-linux-amd64
bin/guard-linux-arm
bin/guard-linux-arm64
bin/guard-windows-amd64.exe
bin/guard-darwin-amd64
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- uses: actions/upload-artifact@master
with:
name: guard-binaries
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.dbg
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@

FROM {ARG_FROM}

ADD bin/{ARG_OS}_{ARG_ARCH}/{ARG_BIN} /{ARG_BIN}
ADD bin/{ARG_BIN}-{ARG_OS}-{ARG_ARCH} /{ARG_BIN}

ENTRYPOINT ["/{ARG_BIN}"]
2 changes: 1 addition & 1 deletion Dockerfile.in
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

FROM {ARG_FROM}

ADD bin/{ARG_OS}_{ARG_ARCH}/{ARG_BIN} /{ARG_BIN}
ADD bin/{ARG_BIN}-{ARG_OS}-{ARG_ARCH} /{ARG_BIN}

# This would be nicer as `nobody:nobody` but distroless has no such entries.
USER 65535:65535
Expand Down
23 changes: 12 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ endif
### These variables should not need tweaking.
###

SRC_PKGS := *.go auth commands docs installer server util
SRC_PKGS := *.go auth authz commands docs installer server util
SRC_DIRS := $(SRC_PKGS) test hack/gendocs # directories which hold app source (not vendored)

DOCKER_PLATFORMS := linux/amd64 linux/arm linux/arm64
Expand All @@ -67,12 +67,13 @@ TAG := $(VERSION)_$(OS)_$(ARCH)
TAG_PROD := $(TAG)
TAG_DBG := $(VERSION)-dbg_$(OS)_$(ARCH)

GO_VERSION ?= 1.13.8
GO_VERSION ?= 1.14.2
BUILD_IMAGE ?= appscode/golang-dev:$(GO_VERSION)

OUTBIN = bin/$(OS)_$(ARCH)/$(BIN)
OUTBIN = bin/$(BIN)-$(OS)-$(ARCH)
ifeq ($(OS),windows)
OUTBIN = bin/$(OS)_$(ARCH)/$(BIN).exe
OUTBIN := bin/$(BIN)-$(OS)-$(ARCH).exe
BIN := $(BIN).exe
endif

# Directories that we need created to build/test.
Expand Down Expand Up @@ -189,7 +190,7 @@ $(OUTBIN): .go/$(OUTBIN).stamp
"
@if [ $(COMPRESS) = yes ] && [ $(OS) != darwin ]; then \
echo "compressing $(OUTBIN)"; \
@docker run \
docker run \
-i \
--rm \
-u $$(id -u):$$(id -g) \
Expand All @@ -201,19 +202,19 @@ $(OUTBIN): .go/$(OUTBIN).stamp
--env HTTP_PROXY=$(HTTP_PROXY) \
--env HTTPS_PROXY=$(HTTPS_PROXY) \
$(BUILD_IMAGE) \
upx --brute /go/$(OUTBIN); \
upx --brute /go/bin/$(BIN); \
fi
@if ! cmp -s .go/$(OUTBIN) $(OUTBIN); then \
mv .go/$(OUTBIN) $(OUTBIN); \
date >$@; \
@if ! cmp -s .go/bin/$(OS)_$(ARCH)/$(BIN) $(OUTBIN); then \
mv .go/bin/$(OS)_$(ARCH)/$(BIN) $(OUTBIN); \
date >$@; \
fi
@echo

# Used to track state in hidden files.
DOTFILE_IMAGE = $(subst /,_,$(IMAGE))-$(TAG)

container: bin/.container-$(DOTFILE_IMAGE)-PROD bin/.container-$(DOTFILE_IMAGE)-DBG
bin/.container-$(DOTFILE_IMAGE)-%: bin/$(OS)_$(ARCH)/$(BIN) $(DOCKERFILE_%)
bin/.container-$(DOTFILE_IMAGE)-%: $(OUTBIN) $(DOCKERFILE_%)
@echo "container: $(IMAGE):$(TAG_$*)"
@sed \
-e 's|{ARG_BIN}|$(BIN)|g' \
Expand Down Expand Up @@ -324,7 +325,7 @@ lint: $(BUILD_DIRS)
--env HTTP_PROXY=$(HTTP_PROXY) \
--env HTTPS_PROXY=$(HTTPS_PROXY) \
--env GO111MODULE=on \
--env GOFLAGS="-mod=vendor" \
--env GOFLAGS="-mod=vendor" \
$(BUILD_IMAGE) \
golangci-lint run --enable $(ADDTL_LINTERS) --deadline=10m --skip-files="generated.*\.go$\" --skip-dirs-use-default --skip-dirs=client,vendor

Expand Down
15 changes: 13 additions & 2 deletions auth/providers/azure/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
"strings"

"github.com/appscode/guard/auth"
"github.com/appscode/guard/auth/providers/azure/graph"
Expand Down Expand Up @@ -161,6 +162,7 @@ func (s Authenticator) Check(token string) (*authv1.UserInfo, error) {
if err != nil {
return nil, err
}

if s.Options.ResolveGroupMembershipOnlyOnOverageClaim {
groups, skipGraphAPI, err := getGroupsAndCheckOverage(claims)
if err != nil {
Expand Down Expand Up @@ -284,7 +286,11 @@ func (c claims) getUserInfo(usernameClaim, userObjectIDClaim string) (*authv1.Us
return nil, errors.Wrap(err, "unable to get username claim")
}

return &authv1.UserInfo{Username: username}, nil
useroid, _ := c.string(userObjectIDClaim)

return &authv1.UserInfo{
Username: username,
Extra: map[string]authv1.ExtraValue{"oid": {useroid}}}, nil
}

// String gets a string value from claims given a key. Returns error if
Expand Down Expand Up @@ -316,9 +322,14 @@ func getAuthInfo(environment, tenantID string, getMetadata func(string, string)
return nil, errors.Wrap(err, "failed to get metadata for azure")
}

msgraphHost := metadata.MsgraphHost
if strings.EqualFold(azure.USGovernmentCloud.Name, environment) {
msgraphHost = "graph.microsoft.us"
}

return &authInfo{
AADEndpoint: env.ActiveDirectoryEndpoint,
MSGraphHost: metadata.MsgraphHost,
MSGraphHost: msgraphHost,
Issuer: metadata.Issuer,
}, nil
}
125 changes: 125 additions & 0 deletions authz/providers/azure/azure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
Copyright The Guard Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package azure

import (
"strings"

"github.com/Azure/go-autorest/autorest/azure"
auth "github.com/appscode/guard/auth/providers/azure"
"github.com/appscode/guard/authz"
"github.com/appscode/guard/authz/providers/azure/rbac"
"github.com/golang/glog"
"github.com/pkg/errors"
authzv1 "k8s.io/api/authorization/v1"
)

const (
OrgType = "azure"
)

func init() {
authz.SupportedOrgs = append(authz.SupportedOrgs, OrgType)
}

type Authorizer struct {
rbacClient *rbac.AccessInfo
}

type authzInfo struct {
AADEndpoint string
ARMEndPoint string
}

func New(opts Options, authopts auth.Options, dataStore authz.Store) (authz.Interface, error) {
c := &Authorizer{}

authzInfoVal, err := getAuthzInfo(authopts.Environment)
if err != nil {
return nil, errors.Wrap(err, "Error in getAuthzInfo %s")
}

switch opts.AuthzMode {
case ARCAuthzMode:
c.rbacClient, err = rbac.New(authopts.ClientID, authopts.ClientSecret, authopts.TenantID, authzInfoVal.AADEndpoint, authzInfoVal.ARMEndPoint, opts.AuthzMode, opts.ResourceId, opts.ARMCallLimit, dataStore, opts.SkipAuthzCheck, opts.AuthzResolveGroupMemberships, opts.SkipAuthzForNonAADUsers)
case AKSAuthzMode:
c.rbacClient, err = rbac.NewWithAKS(opts.AKSAuthzURL, authopts.TenantID, authzInfoVal.ARMEndPoint, opts.AuthzMode, opts.ResourceId, opts.ARMCallLimit, dataStore, opts.SkipAuthzCheck, opts.AuthzResolveGroupMemberships, opts.SkipAuthzForNonAADUsers)
}

if err != nil {
return nil, errors.Wrap(err, "failed to create ms rbac client")
}
return c, nil
}

func (s Authorizer) Check(request *authzv1.SubjectAccessReviewSpec) (*authzv1.SubjectAccessReviewStatus, error) {
if request == nil {
return nil, errors.New("subject access review is nil")
}

// check if user is service account
if strings.HasPrefix(strings.ToLower(request.User), "system") {
glog.V(3).Infof("returning no op to service accounts")
return &authzv1.SubjectAccessReviewStatus{Allowed: false, Reason: rbac.NoOpinionVerdict}, nil
}

if _, ok := request.Extra["oid"]; !ok {
if s.rbacClient.ShouldSkipAuthzCheckForNonAADUsers() {
glog.V(3).Infof("Skip RBAC is set for non AAD users. Returning no opinion for user %s. You may observe this for AAD users for 'can-i' requests.", request.User)
return &authzv1.SubjectAccessReviewStatus{Allowed: false, Reason: rbac.NoOpinionVerdict}, nil
} else {
glog.V(3).Infof("Skip RBAC for non AAD user is not set. Returning deny access for non AAD user %s. You may observe this for AAD users for 'can-i' requests.", request.User)
return &authzv1.SubjectAccessReviewStatus{Allowed: false, Denied: true, Reason: rbac.NotAllowedForNonAADUsers}, nil
}
}

if s.rbacClient.SkipAuthzCheck(request) {
glog.V(3).Infof("user %s is part of skip authz list. returning no op.", request.User)
return &authzv1.SubjectAccessReviewStatus{Allowed: false, Reason: rbac.NoOpinionVerdict}, nil
}

exist, result := s.rbacClient.GetResultFromCache(request)
if exist {
if result {
glog.V(3).Infof("cache hit: returning allowed to user")
return &authzv1.SubjectAccessReviewStatus{Allowed: result, Reason: rbac.AccessAllowedVerdict}, nil
} else {
glog.V(3).Infof("cache hit: returning denied to user")
return &authzv1.SubjectAccessReviewStatus{Allowed: result, Denied: true, Reason: rbac.AccessNotAllowedVerdict}, nil
}
}

if s.rbacClient.IsTokenExpired() {
s.rbacClient.RefreshToken()
}
return s.rbacClient.CheckAccess(request)
}

func getAuthzInfo(environment string) (*authzInfo, error) {
var err error
env := azure.PublicCloud
if environment != "" {
env, err = azure.EnvironmentFromName(environment)
if err != nil {
return nil, errors.Wrap(err, "failed to parse environment for azure")
}
}

return &authzInfo{
AADEndpoint: env.ActiveDirectoryEndpoint,
ARMEndPoint: env.ResourceManagerEndpoint,
}, nil
}
Loading

0 comments on commit 60285b3

Please sign in to comment.