⚠️ Warning: This repository has been moved to AthenZ/athenz-client-sidecar.
This repository is submitted to Athenz Open-Source Community. All ongoing developments and maintenances will continue in the new repository.
# update your local clone
git remote set-url origin https://github.com/AthenZ/athenz-client-sidecar.git
- Athenz client sidecar
- What is Athenz client sidecar
- Use Case
- Specification
- Get N-token from Athenz through client sidecar
- Get access token from Athenz through client sidecar
- Get role token from Athenz through client sidecar
- Get service certificate from Athenz through client sidecar
- Proxy requests and append N-token authentication header
- Proxy requests and append role token authentication header
- Configuration
- Developer Guide
- Deployment Procedure
- License
- Contributor License Agreement
- About releases
- Authors
Athenz client sidecar is an implementation of Kubernetes sidecar container to provide a common interface to retrieve authentication and authorization credential from Athenz server.
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.
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.
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.
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.
GET /ntoken
- Get service token from Athenz
POST /accesstoken
- Get access token from Athenz
POST /roletoken
- Get role token from Athenz
GET /svccert
- Get service certificate from Athenz
/proxy/ntoken
- Append service token to the request header, and send the request to proxy destination
/proxy/roletoken
- Append role token to the request header, and send the request to proxy destination
- 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]"
}
- 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"
}
- 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
}
- 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>"
}
- 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.
- 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.
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.
import (
"encoding/json"
"fmt"
"net/http"
"github.com/yahoojapan/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
}
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"github.com/yahoojapan/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
}
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"github.com/yahoojapan/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
}
import (
"encoding/json"
"fmt"
"net/http"
"github.com/yahoojapan/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
}
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
}
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.
-
Inject client sidecar to your K8s deployment file.
-
Deploy to K8s.
kubectl apply -f injected_deployments.yaml
-
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
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.
This project requires contributors to agree to a Contributor License Agreement (CLA).
Note that only for contributions to the athenz-client-sidecar
repository on the GitHub, the contributors of them shall be deemed to have agreed to the CLA without individual written agreements.