Skip to content
This repository has been archived by the owner on Dec 20, 2022. It is now read-only.

Commit

Permalink
[minor] change package name provider to authorizerd & add JWT Verifei…
Browse files Browse the repository at this point in the history
…r & add RoleCert Verifier (#14)

* [minor] change package name provider to authorizerd & add JWT Verifier & add RoleCert Verifier

Signed-off-by: kpango <[email protected]>

* update dependencies version

* update error handling and update policy logic

* cleanup old policies

* change update policy logic

* change update policy logic

* fix

Signed-off-by: kpango <[email protected]>

* add get policy cache implementation

* fix

* update policyd log and fix test case

* more detail log

* remove unused log and add error message

* add error message

* fix

Signed-off-by: kpango <[email protected]>

* fix

Signed-off-by: kpango <[email protected]>

* fix

Signed-off-by: kpango <[email protected]>

* fix

Signed-off-by: kpango <[email protected]>

* fix test case

* fix test case

* add test case template

* add test case

* rename filename and small fixes

* rename file, generate test code

* add test case

* add testcase

* add test case

* revert changes of logic to cache policy

* update gache version

* add verify role cert test

* fix golint

* add test case

* add test case

* add test case

* add test case;2C

* add test case

* update doc

* add disable daemon

* fix

* fix

* add test case

* fix
  • Loading branch information
Yusuke Kato authored Jul 8, 2019
1 parent 4708e32 commit 62bbcd5
Show file tree
Hide file tree
Showing 40 changed files with 4,465 additions and 1,680 deletions.
36 changes: 18 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
# Athenz policy updater
[![License: Apache](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=flat-square)](https://opensource.org/licenses/Apache-2.0) [![release](https://img.shields.io/github/release/yahoojapan/athenz-policy-updater.svg?style=flat-square)](https://github.com/yahoojapan/athenz-policy-updater/releases/latest) [![CircleCI](https://circleci.com/gh/yahoojapan/athenz-policy-updater.svg)](https://circleci.com/gh/yahoojapan/athenz-policy-updater) [![codecov](https://codecov.io/gh/yahoojapan/athenz-policy-updater/branch/master/graph/badge.svg?token=2CzooNJtUu&style=flat-square)](https://codecov.io/gh/yahoojapan/athenz-policy-updater) [![Go Report Card](https://goreportcard.com/badge/github.com/yahoojapan/athenz-policy-updater)](https://goreportcard.com/report/github.com/yahoojapan/athenz-policy-updater) [![GolangCI](https://golangci.com/badges/github.com/yahoojapan/athenz-policy-updater.svg?style=flat-square)](https://golangci.com/r/github.com/yahoojapan/athenz-policy-updater) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/828220605c43419e92fb0667876dd2d0)](https://www.codacy.com/app/i.can.feel.gravity/athenz-policy-updater?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=yahoojapan/athenz-policy-updater&amp;utm_campaign=Badge_Grade) [![GoDoc](http://godoc.org/github.com/yahoojapan/athenz-policy-updater?status.svg)](http://godoc.org/github.com/yahoojapan/athenz-policy-updater)
## What is Athenz policy updater
# Athenz authorizer
[![License: Apache](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=flat-square)](https://opensource.org/licenses/Apache-2.0) [![release](https://img.shields.io/github/release/yahoojapan/athenz-authorizer.svg?style=flat-square)](https://github.com/yahoojapan/athenz-authorizer/releases/latest) [![CircleCI](https://circleci.com/gh/yahoojapan/athenz-authorizer.svg)](https://circleci.com/gh/yahoojapan/athenz-authorizer) [![codecov](https://codecov.io/gh/yahoojapan/athenz-authorizer/branch/master/graph/badge.svg?token=2CzooNJtUu&style=flat-square)](https://codecov.io/gh/yahoojapan/athenz-authorizer) [![Go Report Card](https://goreportcard.com/badge/github.com/yahoojapan/athenz-authorizer)](https://goreportcard.com/report/github.com/yahoojapan/athenz-authorizer) [![GolangCI](https://golangci.com/badges/github.com/yahoojapan/athenz-authorizer.svg?style=flat-square)](https://golangci.com/r/github.com/yahoojapan/athenz-authorizer) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/828220605c43419e92fb0667876dd2d0)](https://www.codacy.com/app/i.can.feel.gravity/athenz-authorizer?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=yahoojapan/athenz-authorizer&amp;utm_campaign=Badge_Grade) [![GoDoc](http://godoc.org/github.com/yahoojapan/athenz-authorizer?status.svg)](http://godoc.org/github.com/yahoojapan/athenz-authorizer)
## What is Athenz authorizer

Athenz policy updater is a library to cache the policies of [Athenz](https://github.com/yahoo/athenz) to provider authenication and authorization check of user request.
Athenz authorizer is a library to cache the policies of [Athenz](https://github.com/yahoo/athenz) to authorizer authenication and authorization check of user request.

![Overview](./doc/policy_updater_overview.png)

## Usage

To initialize policy updater.
To initialize authorizer.

```golang

// Initialize providerd
daemon, err := providerd.New(
providerd.AthenzURL("www.athenz.io"), // set athenz URL
providerd.AthenzDomains("domain1", "domain2" ... "domain N"), // set athenz domains
providerd.PubkeyRefreshDuration(time.Hour * 24), // set athenz public key refresh duration
providerd.PolicyRefreshDuration(time.Hour), // set policy refresh duration
// Initialize authorizerd
daemon, err := authorizerd.New(
authorizerd.WithAthenzURL("www.athenz.io"), // set athenz URL
authorizerd.WithAthenzDomains("domain1", "domain2" ... "domain N"), // set athenz domains
authorizerd.WithPubkeyRefreshDuration(time.Hour * 24), // set athenz public key refresh duration
authorizerd.WithPolicyRefreshDuration(time.Hour), // set policy refresh duration
)
if err != nil {
// cannot initialize policy updater daemon
// cannot initialize authorizer daemon
}

// Start policy updater daemon
ctx := context.Background() // user can control policy updator daemon lifetime using this context
errs := daemon.StartProviderd(ctx)
// Start authorizer daemon
ctx := context.Background() // user can control authorizer daemon lifetime using this context
errs := daemon.Start(ctx)
go func() {
err := <-errs
// user should handle errors return from the daemon
Expand All @@ -39,9 +39,9 @@ if err := daemon.VerifyRoleToken(ctx, roleTok, act, res); err != nil {

## How it works

To do the authentication and authorization check, the user needs to specify which [domain data](https://github.com/yahoo/athenz/blob/master/docs/data_model.md#data-model) to be cache. The policy updater will periodically refresh the policies and Athenz public key data to [verify and decode]((https://github.com/yahoo/athenz/blob/master/docs/zpu_policy_file.md#zts-signature-validation)) the domain data. The verified domain data will cache into the memory, and use for authentication and authorization check.
To do the authentication and authorization check, the user needs to specify which [domain data](https://github.com/yahoo/athenz/blob/master/docs/data_model.md#data-model) to be cache. The authorizer will periodically refresh the policies and Athenz public key data to [verify and decode]((https://github.com/yahoo/athenz/blob/master/docs/zpu_policy_file.md#zts-signature-validation)) the domain data. The verified domain data will cache into the memory, and use for authentication and authorization check.

The policy updater contains two sub-module, Athenz pubkey daemon (pubkeyd) and Athenz policy daemon (policyd).
The authorizer contains two sub-module, Athenz pubkey daemon (pubkeyd) and Athenz policy daemon (policyd).

### Athenz pubkey daemon

Expand All @@ -53,7 +53,7 @@ Athenz policy daemon (policyd) is responsible for periodically update the policy

## Configuratrion

The policy updater uses functional options pattern to initialize the instance. All the options are defined [here](./option.go).
The authorizer uses functional options pattern to initialize the instance. All the options are defined [here](./option.go).

| Option name | Description | Default Value | Required | Example |
|---------------------------|---------------------------------------------------------------------------------------------------------------------|-------------------------|----------|------------------------|
Expand Down
295 changes: 295 additions & 0 deletions authorizerd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
/*
Copyright (C) 2018 Yahoo Japan Corporation Athenz team.
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 authorizerd

import (
"context"
"crypto/x509"
"net/http"
"strings"
"time"

"github.com/kpango/gache"
"github.com/kpango/glg"

"github.com/pkg/errors"
"github.com/yahoojapan/athenz-authorizer/jwk"
"github.com/yahoojapan/athenz-authorizer/policy"
"github.com/yahoojapan/athenz-authorizer/pubkey"
"github.com/yahoojapan/athenz-authorizer/role"
)

// Authorizerd represents a daemon for user to verify the role token
type Authorizerd interface {
Start(ctx context.Context) <-chan error
VerifyRoleToken(ctx context.Context, tok, act, res string) error
VerifyRoleJWT(ctx context.Context, tok, act, res string) error
VerifyRoleCert(ctx context.Context, peerCerts []*x509.Certificate, act, res string) error
GetPolicyCache(ctx context.Context) map[string]interface{}
}

type authorizer struct {
//
pubkeyd pubkey.Daemon
policyd policy.Daemon
jwkd jwk.Daemon
roleProcessor role.Processor

// common parameters
athenzURL string
client *http.Client

// successful result cache
cache gache.Gache
cacheExp time.Duration

// roleCertURIPrefix
roleCertURIPrefix string

// pubkeyd parameters
disablePubkeyd bool
pubkeyRefreshDuration string
pubkeySysAuthDomain string
pubkeyEtagExpTime string
pubkeyEtagFlushDur string

// policyd parameters
disablePolicyd bool
policyExpireMargin string
athenzDomains []string
policyRefreshDuration string
policyEtagFlushDur string
policyEtagExpTime string

// jwkd parameters
disableJwkd bool
jwkRefreshDuration string
}

type mode uint8

const (
token mode = iota
jwt
)

// New return Authorizerd
// This function will initialize the Authorizerd object with the options
func New(opts ...Option) (Authorizerd, error) {
var (
prov = &authorizer{
cache: gache.New(),
}
err error

pubkeyProvider pubkey.Provider
jwkProvider jwk.Provider
)

for _, opt := range append(defaultOptions, opts...) {
if err = opt(prov); err != nil {
return nil, errors.Wrap(err, "error creating authorizerd")
}
}

if !prov.disablePubkeyd {
if prov.pubkeyd, err = pubkey.New(
pubkey.WithAthenzURL(prov.athenzURL),
pubkey.WithSysAuthDomain(prov.pubkeySysAuthDomain),
pubkey.WithEtagExpTime(prov.pubkeyEtagExpTime),
pubkey.WithEtagFlushDuration(prov.pubkeyEtagFlushDur),
pubkey.WithRefreshDuration(prov.pubkeyRefreshDuration),
pubkey.WithHTTPClient(prov.client),
); err != nil {
return nil, errors.Wrap(err, "error create pubkeyd")
}

pubkeyProvider = prov.pubkeyd.GetProvider()
}

if !prov.disablePolicyd {
if prov.policyd, err = policy.New(
policy.WithExpireMargin(prov.policyExpireMargin),
policy.WithEtagFlushDuration(prov.policyEtagFlushDur),
policy.WithEtagExpTime(prov.policyEtagExpTime),
policy.WithAthenzURL(prov.athenzURL),
policy.WithAthenzDomains(prov.athenzDomains...),
policy.WithRefreshDuration(prov.policyRefreshDuration),
policy.WithHTTPClient(prov.client),
policy.WithPubKeyProvider(prov.pubkeyd.GetProvider()),
); err != nil {
return nil, errors.Wrap(err, "error create policyd")
}
}

if !prov.disableJwkd {
if prov.jwkd, err = jwk.New(
jwk.WithAthenzURL(prov.athenzURL),
jwk.WithRefreshDuration(prov.jwkRefreshDuration),
jwk.WithHTTPClient(prov.client),
); err != nil {
return nil, errors.Wrap(err, "error create jwkd")
}

jwkProvider = prov.jwkd.GetProvider()
}

prov.roleProcessor = role.New(
role.WithPubkeyProvider(pubkeyProvider),
role.WithJWKProvider(jwkProvider))

return prov, nil
}

// Start starts authorizer daemon.
func (p *authorizer) Start(ctx context.Context) <-chan error {
var (
ech = make(chan error, 200)
g = p.cache.StartExpired(ctx, p.cacheExp/2)
cech, pech, jech <-chan error
)

if !p.disablePubkeyd {
cech = p.pubkeyd.Start(ctx)
}
if !p.disablePolicyd {
pech = p.policyd.Start(ctx)
}
if !p.disableJwkd {
jech = p.jwkd.Start(ctx)
}

go func() {
for {
select {
case <-ctx.Done():
g.Stop()
g.Clear()
ech <- ctx.Err()
return
case err := <-cech:
if err != nil {
ech <- errors.Wrap(err, "update pubkey error")
}
case err := <-pech:
if err != nil {
ech <- errors.Wrap(err, "update policy error")
}
case err := <-jech:
if err != nil {
ech <- errors.Wrap(err, "update jwk error")
}
}
}
}()

return ech
}

// VerifyRoleToken verifies the role token for specific resource and return and verification error.
func (p *authorizer) VerifyRoleToken(ctx context.Context, tok, act, res string) error {
return p.verify(ctx, token, tok, act, res)
}

func (p *authorizer) VerifyRoleJWT(ctx context.Context, tok, act, res string) error {
return p.verify(ctx, jwt, tok, act, res)
}

func (p *authorizer) verify(ctx context.Context, m mode, tok, act, res string) error {
if act == "" || res == "" {
return errors.Wrap(ErrInvalidParameters, "empty action / resource")
}

// check if exists in verification success cache
_, ok := p.cache.Get(tok + act + res)
if ok {
glg.Debugf("use cached result. tok: %s, act: %s, res: %s", tok, act, res)
return nil
}

var (
domain string
roles []string
)

switch m {
case token:
rt, err := p.roleProcessor.ParseAndValidateRoleToken(tok)
if err != nil {
glg.Debugf("error parse and validate role token, err: %v", err)
return errors.Wrap(err, "error verify role token")
}
domain = rt.Domain
roles = rt.Roles
case jwt:
rc, err := p.roleProcessor.ParseAndValidateRoleJWT(tok)
if err != nil {
glg.Debugf("error parse and validate role jwt, err: %v", err)
return errors.Wrap(err, "error verify role jwt")
}
domain = rc.Domain
roles = strings.Split(strings.TrimSpace(rc.Role), ",")
}

if err := p.policyd.CheckPolicy(ctx, domain, roles, act, res); err != nil {
glg.Debugf("error check, err: %v", err)
return errors.Wrap(err, "token unauthorizate")
}
glg.Debugf("set roletoken result. tok: %s, act: %s, res: %s", tok, act, res)
p.cache.SetWithExpire(tok+act+res, struct{}{}, p.cacheExp)
return nil
}

func (p *authorizer) VerifyRoleCert(ctx context.Context, peerCerts []*x509.Certificate, act, res string) error {
dr := make([]string, 0, 2)
drcheck := make(map[string]struct{})
domainRoles := make(map[string][]string)
for _, cert := range peerCerts {
for _, uri := range cert.URIs {
if strings.HasPrefix(uri.String(), p.roleCertURIPrefix) {
dr = strings.SplitN(strings.TrimPrefix(uri.String(), p.roleCertURIPrefix), "/", 2) // domain/role
if len(dr) != 2 {
continue
}
domain, roleName := dr[0], dr[1]
// duplicated role check
if _, ok := drcheck[domain+roleName]; !ok {
domainRoles[domain] = append(domainRoles[domain], roleName)
drcheck[domain+roleName] = struct{}{}
}
}
}
}

if len(domainRoles) == 0 {
return errors.New("not valid role certificate")
}

var err error
for domain, roles := range domainRoles {
// TODO futurework
if err = p.policyd.CheckPolicy(ctx, domain, roles, act, res); err == nil {
return nil
}
}

return errors.Wrap(err, "role certificates unauthorizate")
}

func (p *authorizer) GetPolicyCache(ctx context.Context) map[string]interface{} {
return p.policyd.GetPolicyCache(ctx)

}
Loading

0 comments on commit 62bbcd5

Please sign in to comment.