-
Notifications
You must be signed in to change notification settings - Fork 276
/
zpu_client.go
260 lines (240 loc) · 8.44 KB
/
zpu_client.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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
// Copyright 2017 Yahoo Holdings, Inc.
// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms.
package zpu
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
"time"
"github.com/ardielle/ardielle-go/rdl"
"github.com/AthenZ/athenz/clients/go/zts"
"github.com/AthenZ/athenz/libs/go/athenzutils"
"github.com/AthenZ/athenz/libs/go/zmssvctoken"
"github.com/AthenZ/athenz/utils/zpe-updater/util"
)
func PolicyUpdater(config *ZpuConfiguration) error {
if config == nil {
return errors.New("Nil configuration")
}
if config.DomainList == "" {
return errors.New("No domain list to process from configuration")
}
if config.Zts == "" {
return errors.New("Empty Zts url in configuration")
}
success := true
domains := strings.Split(config.DomainList, ",")
ztsURL := formatURL(config.Zts, "zts/v1")
var ztsClient zts.ZTSClient
if config.PrivateKeyFile != "" && config.CertFile != "" {
ztsCli, err := athenzutils.ZtsClient(ztsURL, config.PrivateKeyFile, config.CertFile, config.CaCertFile, config.Proxy)
if err != nil {
return fmt.Errorf("Failed to create Zts Client, Error:%v", err)
}
ztsClient = *ztsCli
} else if config.PrivateKeyFile == "" && config.CertFile != "" {
return errors.New("Both private key and cert file are required, missing private key file")
} else if config.PrivateKeyFile != "" && config.CertFile == "" {
return errors.New("Both private key and cert file are required, missing certificate file")
} else {
ztsClient = zts.NewClient(ztsURL, nil)
}
policyFileDir := config.PolicyFileDir
failedDomains := ""
for _, domain := range domains {
err := GetPolicies(config, ztsClient, policyFileDir, domain)
if err != nil {
if success {
success = false
}
failedDomains += `"`
failedDomains += domain
failedDomains += `" `
log.Printf("Failed to get policies for domain: %v, Error:%v", domain, err)
}
}
if !success {
return fmt.Errorf("Failed to get policies for domains: %v", failedDomains)
}
return nil
}
func GetPolicies(config *ZpuConfiguration, ztsClient zts.ZTSClient, policyFileDir, domain string) error {
log.Printf("Getting policies for domain: %v", domain)
etag := GetEtagForExistingPolicy(config, ztsClient, domain, policyFileDir)
data, _, err := ztsClient.GetDomainSignedPolicyData(zts.DomainName(domain), etag)
if err != nil {
return fmt.Errorf("Failed to get domain signed policy data for domain: %v, Error:%v", domain, err)
}
if data == nil {
if etag != "" {
log.Printf("Policies not updated since last fetch for domain: %v", domain)
return nil
}
return fmt.Errorf("Empty policies data returned for domain: %v", domain)
}
// validate data using zts public key and signature
bytes, err := ValidateSignedPolicies(config, ztsClient, data)
if err != nil {
return fmt.Errorf("Failed to validate policy data for domain: %v, Error: %v", domain, err)
}
err = WritePolicies(config, bytes, domain, policyFileDir)
if err != nil {
return fmt.Errorf("Unable to write Policies for domain:\"%v\" to file, Error:%v", domain, err)
}
log.Printf("Policies for domain: %v successfully written", domain)
return nil
}
func GetEtagForExistingPolicy(config *ZpuConfiguration, ztsClient zts.ZTSClient, domain, policyFileDir string) string {
var etag string
var domainSignedPolicyData *zts.DomainSignedPolicyData
policyFile := fmt.Sprintf("%s/%s.pol", policyFileDir, domain)
// If Policies file is not found, return empty etag the first time.
// Otherwise load the file contents, if data has expired return empty etag,
// else construct etag from modified field in JSON.
exists := util.Exists(policyFile)
if !exists {
return ""
}
readFile, err := os.OpenFile(policyFile, os.O_RDONLY, 0444)
defer readFile.Close()
if err != nil {
return ""
}
err = json.NewDecoder(readFile).Decode(&domainSignedPolicyData)
if err != nil {
return ""
}
_, err = ValidateSignedPolicies(config, ztsClient, domainSignedPolicyData)
if err != nil {
return ""
}
expires := domainSignedPolicyData.SignedPolicyData.Expires
// We are going to see if we should consider the policy expired
// and retrieve the latest policy. We're going to take the current
// expiry timestamp from the policy file, subtract the expected
// expiry check time (default 2 days) and then see if the date we
// get should be considered as expired.
if expired(expires, config.ExpiryCheck) {
return ""
}
modified := domainSignedPolicyData.SignedPolicyData.Modified
if !modified.IsZero() {
etag = "\"" + string(modified.String()) + "\""
}
return etag
}
func ValidateSignedPolicies(config *ZpuConfiguration, ztsClient zts.ZTSClient, data *zts.DomainSignedPolicyData) ([]byte, error) {
expires := data.SignedPolicyData.Expires
if expired(expires, 0) {
return nil, fmt.Errorf("The policy data is expired on %v", expires)
}
signedPolicyData := data.SignedPolicyData
ztsSignature := data.Signature
ztsKeyID := data.KeyId
ztsPublicKey := config.GetZtsPublicKey(ztsKeyID)
if ztsPublicKey == "" {
key, err := ztsClient.GetPublicKeyEntry("sys.auth", "zts", ztsKeyID)
if err != nil {
return nil, fmt.Errorf("Unable to get the Zts public key with id:\"%v\" to verify data", ztsKeyID)
}
decodedKey, err := new(zmssvctoken.YBase64).DecodeString(key.Key)
if err != nil {
return nil, fmt.Errorf("Unable to decode the Zts public key with id:\"%v\" to verify data", ztsKeyID)
}
ztsPublicKey = string(decodedKey)
}
input, err := util.ToCanonicalString(signedPolicyData)
if err != nil {
return nil, err
}
err = verify(input, ztsSignature, ztsPublicKey)
if err != nil {
return nil, fmt.Errorf("Verification of data with zts key having id:\"%v\" failed, Error :%v", ztsKeyID, err)
}
//generate canonical json output so that properties
//can validate the signatures if not using athenz
//provided libraries for authorization
bytes := []byte("{\"signedPolicyData\":" + input + ",\"keyId\":\"" + ztsKeyID + "\",\"signature\":\"" + ztsSignature + "\"}")
zmsSignature := data.SignedPolicyData.ZmsSignature
zmsKeyID := data.SignedPolicyData.ZmsKeyId
zmsPublicKey := config.GetZmsPublicKey(zmsKeyID)
if zmsPublicKey == "" {
key, err := ztsClient.GetPublicKeyEntry("sys.auth", "zms", zmsKeyID)
if err != nil {
return nil, fmt.Errorf("Unable to get the Zms public key with id:\"%v\" to verify data", zmsKeyID)
}
decodedKey, err := new(zmssvctoken.YBase64).DecodeString(key.Key)
if err != nil {
return nil, fmt.Errorf("Unable to decode the Zms public key with id:\"%v\" to verify data", zmsKeyID)
}
zmsPublicKey = string(decodedKey)
}
policyData := data.SignedPolicyData.PolicyData
input, err = util.ToCanonicalString(policyData)
if err != nil {
return nil, err
}
err = verify(input, zmsSignature, zmsPublicKey)
if err != nil {
return nil, fmt.Errorf("Verification of data with zms key with id:\"%v\" failed, Error :%v", zmsKeyID, err)
}
return bytes, nil
}
func verify(input, signature, publicKey string) error {
verifier, err := zmssvctoken.NewVerifier([]byte(publicKey))
if err != nil {
return err
}
err = verifier.Verify(input, signature)
return err
}
func expired(expires rdl.Timestamp, offset int) bool {
expiryCheck := rdl.NewTimestamp(expires.Time.Add(-1 * time.Duration(int64(offset)) * time.Second))
return rdl.TimestampNow().Millis() > expiryCheck.Millis()
}
// If domain policy file is not found, create the policy file and write policies in it.
// Else delete the existing file and write the modified policies to new file.
func WritePolicies(config *ZpuConfiguration, bytes []byte, domain, policyFileDir string) error {
tempPolicyFileDir := config.TempPolicyFileDir
if tempPolicyFileDir == "" || bytes == nil {
return errors.New("Empty parameters are not valid arguments")
}
policyFile := fmt.Sprintf("%s/%s.pol", policyFileDir, domain)
tempPolicyFile := fmt.Sprintf("%s/%s.tmp", tempPolicyFileDir, domain)
if util.Exists(tempPolicyFile) {
err := os.Remove(tempPolicyFile)
if err != nil {
return err
}
}
err := verifyTmpDirSetup(tempPolicyFileDir)
if err != nil {
return err
}
err = ioutil.WriteFile(tempPolicyFile, bytes, 0755)
if err != nil {
return err
}
err = os.Rename(tempPolicyFile, policyFile)
return err
}
func verifyTmpDirSetup(TempPolicyFileDir string) error {
if util.Exists(TempPolicyFileDir) {
return nil
}
err := os.MkdirAll(TempPolicyFileDir, 0755)
return err
}
func formatURL(url, suffix string) string {
if !strings.HasSuffix(url, suffix) {
if strings.LastIndex(url, "/") != len(url)-1 {
url += "/"
}
url += suffix
}
return url
}