-
Notifications
You must be signed in to change notification settings - Fork 276
/
role-token.go
178 lines (154 loc) · 4.25 KB
/
role-token.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// Copyright The Athenz Authors
// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms.
package ztsroletoken
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net/http"
"os"
"sync"
"time"
"github.com/AthenZ/athenz/clients/go/zts"
"github.com/AthenZ/athenz/libs/go/zmssvctoken"
)
const (
defaultPrincipalAuthHeader = "Athenz-Principal-Auth"
)
var expirationDrift = 10 * time.Minute
// RoleToken is a mechanism to get a role token (ztoken)
// as a string. It guarantees that the returned token has
// not expired.
type RoleToken interface {
RoleTokenValue() (string, error)
}
// RoleTokenOptions allows the caller to supply additional options
// for getting a role token. The zero-value is a valid configuration.
type RoleTokenOptions struct {
BaseZTSURL string // the base ZTS URL to use
Role string // the single role for which a token is required
MinExpire time.Duration // the minimum expiry of the token in (server default if zero)
MaxExpire time.Duration // the maximum expiry of the token (server default if zero)
AuthHeader string // Auth Header to use while making ZMS calls
CACert []byte // Optional CA certpem to validate the ZTS server
}
type roleToken struct {
domain string
opts RoleTokenOptions
l sync.RWMutex
tok zmssvctoken.Token
certFile string
keyFile string
zToken string
expireTime time.Time
}
func getClientTLSConfig(certFile, keyFile string) (*tls.Config, error) {
certpem, err := os.ReadFile(certFile)
if err != nil {
return nil, err
}
keypem, err := os.ReadFile(keyFile)
if err != nil {
return nil, err
}
clientCert, err := tls.X509KeyPair(certpem, keypem)
if err != nil {
return nil, err
}
config := &tls.Config{}
config.Certificates = make([]tls.Certificate, 1)
config.Certificates[0] = clientCert
return config, nil
}
func (r *roleToken) updateRoleToken() (string, error) {
durationToExpireSeconds := func(d time.Duration) *int32 {
if d == 0 {
return nil
}
e := int32(d / time.Second)
return &e
}
if r.opts.BaseZTSURL == "" {
return "", errors.New("BaseZTSURL is empty")
}
r.l.Lock()
defer r.l.Unlock()
var z zts.ZTSClient
if r.certFile != "" && r.keyFile != "" {
// Use ZTS Client with TLS cert
config, err := getClientTLSConfig(r.certFile, r.keyFile)
if err != nil {
return "", err
}
if len(r.opts.CACert) != 0 {
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(r.opts.CACert) {
return "", fmt.Errorf("Failed to append certs to pool")
}
config.RootCAs = certPool
}
z = zts.NewClient(r.opts.BaseZTSURL, &http.Transport{
TLSClientConfig: config,
})
} else {
ntoken, err := r.tok.Value()
if err != nil {
return "", err
}
z = zts.NewClient(r.opts.BaseZTSURL, nil)
z.AddCredentials(r.opts.AuthHeader, ntoken)
}
rt, err := z.GetRoleToken(
zts.DomainName(r.domain),
zts.EntityList(r.opts.Role),
durationToExpireSeconds(r.opts.MinExpire),
durationToExpireSeconds(r.opts.MaxExpire),
zts.EntityName(""),
)
if err != nil {
return "", err
}
r.zToken = rt.Token
r.expireTime = time.Unix(rt.ExpiryTime, 0)
return r.zToken, nil
}
func (r *roleToken) RoleTokenValue() (string, error) {
r.l.RLock()
ztok := r.zToken
e := r.expireTime
r.l.RUnlock()
if time.Now().Add(expirationDrift).After(e) {
return r.updateRoleToken()
}
return ztok, nil
}
// NewRoleToken returns a RoleToken implementation based on principal tokens
// retrieved from the supplied Token implementation for the supplied domain
// and options.
func NewRoleToken(tok zmssvctoken.Token, domain string, opts RoleTokenOptions) *roleToken {
if opts.AuthHeader == "" {
opts.AuthHeader = defaultPrincipalAuthHeader
}
rt := &roleToken{
tok: tok,
domain: domain,
opts: opts,
}
return rt
}
// NewRoleTokenFromCert returns a RoleToken implementation based on principal service certificate
// retrieved from the supplied service certificate for the supplied domain
// and options.
func NewRoleTokenFromCert(certFile, keyFile, domain string, opts RoleTokenOptions) *roleToken {
if opts.AuthHeader == "" {
opts.AuthHeader = defaultPrincipalAuthHeader
}
rt := &roleToken{
certFile: certFile,
keyFile: keyFile,
domain: domain,
opts: opts,
}
return rt
}