github.com/AthenZ/athenz-client-sidecar/v2

Client sidecar for fetching Athenz credentials


Keywords
athenz, authentication, authorization, go, kubernetes, sidecar
License
Apache-2.0
Install
go get github.com/AthenZ/athenz-client-sidecar/v2

Documentation

Athenz client sidecar

License: Apache GitHub release (latest by date) Docker Image Version (tag latest) Go Report Card GoDoc Contributor Covenant

logo

What is Athenz client sidecar

Athenz client sidecar is an implementation of Kubernetes sidecar container to provide a common interface to retrieve authentication and authorization credential from Athenz server.

Get Athenz N-token from client sidecar

Sidecar architecture (get N-token)

Whenever user wants to get the N-token, user does not need to focus on extra logic to generate token, user can access client sidecar container instead of implementing the logic themselves, to avoid the extra logic implemented by user. For instance, the client sidecar container caches the token and periodically generates the token automatically. For user this logic is transparent, but it improves the overall performance as it does not generate the token every time whenever the user asks for it.

Get Athenz Access Token from client sidecar

Sidecar architecture (get Access token)

User can get the access token from the client sidecar container. Whenever user requests for the access token, the sidecar process will get the access token from Athenz if it is not in the cache, and cache it in memory. The background thread will update corresponding access token periodically.

Get Athenz Role Token from client sidecar

Sidecar architecture (get Role token)

User can get the role token from the client sidecar container. Whenever user requests for the role token, the sidecar process will get the role token from Athenz if it is not in the cache, and cache it in memory. The background thread will update corresponding role token periodically.

Proxy HTTP request (add corresponding Athenz authorization token)

Sidecar architecture (proxy request)

User can also use the reverse proxy endpoint to proxy the request to another server that supports Athenz token validation. The proxy endpoint will append the necessary authorization (N-token or role token) HTTP header to the request and proxy the request to the destination server. User does not need to care about the token generation logic where this sidecar container will handle it, also it supports similar caching mechanism with the N-token usage.


Use Case

  1. GET /ntoken
    • Get service token from Athenz
  2. POST /accesstoken
    • Get access token from Athenz
  3. POST /roletoken
    • Get role token from Athenz
  4. GET /svccert
    • Get service certificate from Athenz
  5. /proxy/ntoken
    • Append service token to the request header, and send the request to proxy destination
  6. /proxy/roletoken
    • Append role token to the request header, and send the request to proxy destination

Specification

Get N-token from Athenz through client sidecar

  • Only Accept HTTP GET request.
  • Response body contains below information in JSON format.
Name Description Example
token The N-token generated v=S1;d=client;n=service;h=localhost;a=6996e6fc49915494;t=1486004464;e=1486008064;k=0;s=[signature]

Example:

{
  "token": "v=S1;d=client;n=service;h=localhost;a=6996e6fc49915494;t=1486004464;e=1486008064;k=0;s=[signature]"
}

Get access token from Athenz through client sidecar

  • Only accept HTTP POST request.
  • Request body must contains below information in JSON format.
Name Description Required? Example
domain Access token domain name Yes domain.shopping
role Access token role name (comma separated list) No user
proxy_for_principal Access token proxyForPrincipal name No proxyForPrincipal
expiry Access token expiry time (in second) No 1000

Example:

{
  "domain": "domain.shopping",
  "role": "user",
  "proxy_for_principal": "proxyForPrincipal",
  "expiry": 1000
}
  • Response body contains below information in JSON format.
Name Description Example
access_token Access token eyJraWQiOiIwIiwidHlwIjoiYXQrand0IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJkb21haW4udHJhdmVsLnRyYXZlbC1zaXRlIiwiaWF0IjoxNTgzNzE0NzA0LCJleHAiOjE1ODM3MTY1MDQsImlzcyI6Imh0dHBzOi8venRzLmF0aGVuei5pbyIsImF1ZCI6ImRvbWFpbi5zaG9wcGluZyIsImF1dGhfdGltZSI6MTU4MzcxNDcwNCwidmVyIjoxLCJzY3AiOlsidXNlcnMiXSwidWlkIjoiZG9tYWluLnRyYXZlbC50cmF2ZWwtc2l0ZSIsImNsaWVudF9pZCI6ImRvbWFpbi50cmF2ZWwudHJhdmVsLXNpdGUifQ.[signature]
token_type Access token token type Bearer
expires_in Access token expiry time (in second) 1000
scope Access token scope (Only added if role is not specified, space separated) domain.shopping:role.user

Example:

{
  "access_token": "eyJraWQiOiIwIiwidHlwIjoiYXQrand0IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJkb21haW4udHJhdmVsLnRyYXZlbC1zaXRlIiwiaWF0IjoxNTgzNzE0NzA0LCJleHAiOjE1ODM3MTY1MDQsImlzcyI6Imh0dHBzOi8venRzLmF0aGVuei5pbyIsImF1ZCI6ImRvbWFpbi5zaG9wcGluZyIsImF1dGhfdGltZSI6MTU4MzcxNDcwNCwidmVyIjoxLCJzY3AiOlsidXNlcnMiXSwidWlkIjoiZG9tYWluLnRyYXZlbC50cmF2ZWwtc2l0ZSIsImNsaWVudF9pZCI6ImRvbWFpbi50cmF2ZWwudHJhdmVsLXNpdGUifQ.F2x9_Q4GRmgRAXB0_tQRAWSwfJ9W3VtIoIVP1F4R19Ah8x1ml8jbxe88auOGmdElR8Gd2oQBNGMSyTkBgVBi9lRmYRpvYI94DN27zy5ZQzAPx_GgWshCbv8ebK9mHmcHkvGjJQzvoc7mgtKSRCZB4fC8-95c8Nb3BlebXWOz9evhO-xlkt5QYcavvSBzU6gNzZ7IjANTwIh4_iES-drWZOZ_yg4WS9wMpk1ycJRsdr5En5QMwQJEzcMRL-5-D8gLChXEESFSsY86ekd-fXOncP1N-V1xjfVURw_TzWKiIj6DFwRsMV1dTm9ffZC0tFKOKe9M3sUYdfkm0qWuEqLjfA",
  "token_type": "Bearer",
  "expires_in": 1000,
  "scope": "domain.shopping:role.user"
}

Get role token from Athenz through client sidecar

  • Only accept HTTP POST request.
  • Request body must contains below information in JSON format.
Name Description Required? Example
domain Role token domain name Yes domain.shopping
role Role token role name (comma separated list) No users
proxy_for_principal Role token proxyForPrincipal name No proxyForPrincipal
min_expiry Role token minimal expiry time (in second) No 100
max_expiry Role token maximum expiry time (in second) No 1000

Example:

{
  "domain": "domain.shopping",
  "role": "users",
  "proxy_for_principal": "proxyForPrincipal",
  "min_expiry": 100,
  "max_expiry": 1000
}
  • Response body contains below information in JSON format.
Name Description Example
token Role token v=Z1;d=domain.shopping;r=users;p=domain.travel.travel-site;h=athenz.co.jp;a=9109ee08b79e6b63;t=1528853625;e=1528860825;k=0;i=192.168.1.1;s=[signature]
expiryTime Role token expiry time (unix timestamp) 1528860825

Example:

{
  "token": "v=Z1;d=domain.shopping;r=users;p=domain.travel.travel-site;h=athenz.co.jp;a=9109ee08b79e6b63;t=1528853625;e=1528860825;k=0;i=192.168.1.1;s=s9WwmhDeO_En3dvAKvh7OKoUserfqJ0LT5Pct5Gfw5lKNKGH4vgsHLI1t0JFSQJWA1ij9ay_vWw1eKaiESfNJQOKPjAANdFZlcXqCCRUCuyAKlbX6KmWtQ9JaKSkCS8a6ReOuAmCToSqHf3STdKYF2tv1ZN17ic4se4VmT5aTig-",
  "expiryTime": 1528860825
}

Get service certificate from Athenz through client sidecar

  • Only Accept HTTP GET request.
  • Response body contains below information in JSON format.
Name Description Example
cert Service certificate <certificate in PEM format>

Example:

{
  "cert": "<certificate in PEM format>"
}

Proxy requests and append N-token authentication header

  • Accept any HTTP request.
  • Athenz client sidecar will proxy the request and append the N-token to the request header.
  • The destination server will return back to user via proxy.

Proxy requests and append role token authentication header

  • Accept any HTTP request.
  • Request header must contains below information.
Name Description Required? Example
Athenz-Role The user role name used to generate the role token Yes users
Athenz-Domain The domain name used to generate the role token Yes provider
Athenz-Proxy-Principal The proxy for principal name used to generate the role token Yes username

HTTP header Example:

Athenz-Role: users
Athenz-Domain: provider
Athenz-Proxy-Principal: username
  • The destination server will return back to user via proxy.

Configuration

Developer Guide

After injecting client sidecar to user application, user application can access the client sidecar to get authorization and authentication credential from Athenz server. The client sidecar can only access by the user application injected, other application cannot access to the client sidecar. User can access client sidecar by using HTTP request.

Example code

Get N-token from client sidecar

import (
    "encoding/json"
    "fmt"
    "net/http"

    "github.com/AthenZ/athenz-client-sidecar/v2/model"
)

const scURL = "127.0.0.1" // sidecar URL
const scPort = "8081"

type NTokenResponse = model.NTokenResponse

func GetNToken() (*NTokenResponse, error) {
    url := fmt.Sprintf("http://%s:%s/ntoken", scURL, scPort)

    // make request
    res, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()

    // validate response
    if res.StatusCode != http.StatusOK {
        err = fmt.Errorf("%s returned status code %d", url, res.StatusCode)
        return nil, err
    }

    // decode request
    var data NTokenResponse
    err = json.NewDecoder(res.Body).Decode(&data)
    if err != nil {
        return nil, err
    }

    return &data, nil
}

Get access token from client sidecar

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"

    "github.com/AthenZ/athenz-client-sidecar/v2/model"
)

const scURL = "127.0.0.1" // sidecar URL
const scPort = "8081"

type AccessRequest = model.AccessRequest
type AccessResponse = model.AccessResponse

func GetAccessToken(domain, role, proxyForPrincipal string, expiry int64) (*AccessResponse, error) {
    url := fmt.Sprintf("http://%s:%s/accesstoken", scURL, scPort)

    r := &AccessRequest{
        Domain:            domain,
        Role:              role,
        ProxyForPrincipal: proxyForPrincipal,
        Expiry:            expiry,
    }
    reqJSON, _ := json.Marshal(r)

    // create POST request
    req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(reqJSON))
    if err != nil {
        return nil, err
    }
    req.Header.Set("Content-Type", "application/json")

    // make request
    res, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()

    // validate response
    if res.StatusCode != http.StatusOK {
        err = fmt.Errorf("%s returned status code %d", url, res.StatusCode)
        return nil, err
    }

    // decode request
    var data AccessResponse
    err = json.NewDecoder(res.Body).Decode(&data)
    if err != nil {
        return nil, err
    }

    return &data, nil
}

Get role token from client sidecar

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"

    "github.com/AthenZ/athenz-client-sidecar/v2/model"
)

const scURL = "127.0.0.1" // sidecar URL
const scPort = "8081"

type RoleRequest = model.RoleRequest
type RoleResponse = model.RoleResponse

func GetRoleToken(domain, role, proxyForPrincipal string, minExpiry, maxExpiry int64) (*RoleResponse, error) {
    url := fmt.Sprintf("http://%s:%s/roletoken", scURL, scPort)

    r := &RoleRequest{
        Domain:            domain,
        Role:              role,
        ProxyForPrincipal: proxyForPrincipal,
        MinExpiry:         minExpiry,
        MaxExpiry:         maxExpiry,
    }
    reqJSON, _ := json.Marshal(r)

    // create POST request
    req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(reqJSON))
    if err != nil {
        return nil, err
    }
    req.Header.Set("Content-Type", "application/json")

    // make request
    res, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()

    // validate response
    if res.StatusCode != http.StatusOK {
        err = fmt.Errorf("%s returned status code %d", url, res.StatusCode)
        return nil, err
    }

    // decode request
    var data RoleResponse
    err = json.NewDecoder(res.Body).Decode(&data)
    if err != nil {
        return nil, err
    }

    return &data, nil
}

Get service certificate from client sidecar

import (
    "encoding/json"
    "fmt"
    "net/http"

    "github.com/AthenZ/athenz-client-sidecar/v2/model"
)

const scURL = "127.0.0.1" // sidecar URL
const scPort = "8081"

type SvcCertResponse = model.SvcCertResponse

func GetSvcCert() (*SvcCertResponse, error) {
    url := fmt.Sprintf("http://%s:%s/svccert", scURL, scPort)

    // make request
    res, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()

    // validate response
    if res.StatusCode != http.StatusOK {
        err = fmt.Errorf("%s returned status code %d", url, res.StatusCode)
        return nil, err
    }

    // decode request
    var data SvcCertResponse
    err = json.NewDecoder(res.Body).Decode(&data)
    if err != nil {
        return nil, err
    }

    return &data, nil
}

Proxy request through client sidecar (append N-token)

const (
    scURL  = "127.0.0.1" // sidecar URL
    scPort = "8081"
)

var (
    httpClient *http.Client // the HTTP client that use the proxy to append N-token header

    // proxy URL
    proxyNTokenURL    = fmt.Sprintf("http://%s:%s/proxy/ntoken", scURL, scPort)
)

func initHTTPClient() error {
    proxyURL, err := url.Parse(proxyNTokenURL)
    if err != nil {
        return err
    }

    // transport that use the proxy, and append to the client
    transport := &http.Transport{
        Proxy: http.ProxyURL(proxyURL),
    }
    httpClient = &http.Client{
        Transport: transport,
    }

    return nil
}

func MakeRequestUsingProxy(method, targetURL string, body io.Reader) (*[]byte, error) {
    // create POST request
    req, err := http.NewRequest(method, targetURL, body)
    if err != nil {
        return nil, err
    }

    // make request through the proxy
    res, err := httpClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()

    // validate response
    if res.StatusCode != http.StatusOK {
        err = fmt.Errorf("%s returned status code %d", targetURL, res.StatusCode)
        return nil, err
    }

    // process response
    data, err := ioutil.ReadAll(res.Body)
    if err != nil {
        return nil, err
    }

    return &data, nil
}

Proxy request through client sidecar (append role token)

const (
    scURL  = "127.0.0.1" // sidecar URL
    scPort = "8081"
)

var (
    httpClient *http.Client // the HTTP client that use the proxy to append role token header

    // proxy URL
    proxyRoleTokenURL = fmt.Sprintf("http://%s:%s/proxy/roletoken", scURL, scPort)
)

func initHTTPClient() error {
    proxyURL, err := url.Parse(proxyRoleTokenURL)
    if err != nil {
        return err
    }

    // transport that use the proxy, and append to the client
    transport := &http.Transport{
        Proxy: http.ProxyURL(proxyURL),
    }
    httpClient = &http.Client{
        Transport: transport,
    }

    return nil
}

func MakeRequestUsingProxy(method, targetURL string, body io.Reader, role, domain, proxyPrincipal string) (*[]byte, error) {
    // create POST request
    req, err := http.NewRequest(method, targetURL, body)
    if err != nil {
        return nil, err
    }

    // append header for the proxy
    req.Header.Set("Athenz-Role", role)
    req.Header.Set("Athenz-Domain", domain)
    req.Header.Set("Athenz-Proxy-Principal", proxyPrincipal)

    // make request through the proxy
    res, err := httpClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()

    // validate response
    if res.StatusCode != http.StatusOK {
        err = fmt.Errorf("%s returned status code %d", targetURL, res.StatusCode)
        return nil, err
    }

    // process response
    data, err := ioutil.ReadAll(res.Body)
    if err != nil {
        return nil, err
    }

    return &data, nil
}

We only provided golang example, but user can implement a client using any other language and connect to sidecar container using HTTP request.

Deployment Procedure

  1. Inject client sidecar to your K8s deployment file.

  2. Deploy to K8s.

    kubectl apply -f injected_deployments.yaml
  3. Verify if the application running

    # list all the pods
    kubectl get pods -n <namespace>
    # if you are not sure which namespace your application deployed, use `--all-namespaces` option
    kubectl get pods --all-namespaces
    
    # describe the pod to show detail information
    kubectl describe pods <pod_name>
    
    # check application logs
    kubectl logs <pod_name> -c <container_name>
    # e.g. to show client sidecar logs
    kubectl logs nginx-deployment-6cc8764f9c-5c6hm -c athenz-client-sidecar

About releases

  • Releases
    • GitHub release (latest by date)
    • Docker Image Version (tag latest)

Authors