-
Notifications
You must be signed in to change notification settings - Fork 0
/
credential.go
169 lines (151 loc) · 4.85 KB
/
credential.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
/*******************************************************************************
*
* Copyright 2020 SAP SE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You should have received a copy of the License along with this
* program. If not, 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 main
import (
"crypto/md5" //nolint:gosec // used in a none security relevant way
"encoding/hex"
"encoding/json"
"fmt"
"reflect"
"sort"
"strings"
"github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens"
"github.com/prometheus/client_golang/prometheus"
"github.com/sapcc/go-bits/logg"
)
// CredentialID identifies an EC2 credential.
type CredentialID struct {
UserID string
AccessKey string
}
// String recovers the string representation of this CredentialID.
func (cred CredentialID) String() string {
return fmt.Sprintf("%s:%s", cred.UserID, cred.AccessKey)
}
// CacheKey returns the key under which this credential's payload is stored in memcache.
func (cred CredentialID) CacheKey() string {
rawKey := "s3secret/" + cred.AccessKey
hashBytes := md5.Sum([]byte(rawKey)) //nolint:gosec // input cannot be chosen by the user
return hex.EncodeToString(hashBytes[:])
}
// AsLabels represents this credential as a set of Prometheus labels.
func (cred CredentialID) AsLabels() prometheus.Labels {
return prometheus.Labels{
"userid": cred.UserID,
"accesskey": cred.AccessKey,
}
}
// MustParseCredentials parses CredentialIDs passed in as CLI arguments.
func MustParseCredentials(args []string) []CredentialID {
result := make([]CredentialID, len(args))
for idx, arg := range args {
fields := strings.Split(arg, ":")
if len(fields) != 2 {
logg.Fatal("cannot parse userid:accesskey pair: %q", arg)
}
cred := CredentialID{
UserID: strings.TrimSpace(fields[0]),
AccessKey: strings.TrimSpace(fields[1]),
}
if cred.UserID == "" || cred.AccessKey == "" {
logg.Fatal("cannot parse userid:accesskey pair: %q", arg)
}
result[idx] = cred
}
return result
}
// CredentialPayload contains the payload for a credential which we write into memcached.
type CredentialPayload struct {
Headers map[string]string
Project tokens.Project
Secret string
}
// MarshalJSON implements the json.Marshaler interface.
func (p CredentialPayload) MarshalJSON() ([]byte, error) {
return json.Marshal([]interface{}{p.Headers, p.Project, p.Secret})
}
// UnmarshalJSON implements the json.Marshaler interface.
func (p *CredentialPayload) UnmarshalJSON(buf []byte) error {
var fields []json.RawMessage
err := json.Unmarshal(buf, &fields)
if err != nil {
return err
}
if len(fields) != 3 {
return fmt.Errorf("expected CredentialPayload with 3 elements, got %d elements", len(fields))
}
err = json.Unmarshal([]byte(fields[0]), &p.Headers)
if err != nil {
return err
}
err = json.Unmarshal([]byte(fields[1]), &p.Project)
if err != nil {
return err
}
err = json.Unmarshal([]byte(fields[2]), &p.Secret)
if err != nil {
return err
}
return nil
}
// EqualTo is similar to reflect.DeepEqual(), but considers some additional invariants.
func (p *CredentialPayload) EqualTo(other *CredentialPayload) bool {
if (p == nil) != (other == nil) {
return false
}
if p == nil {
// therefore also `other == nil`
return true
}
// Now we know that `p != nil && other != nil`.
// make a deep copy of the RHS
rhs := &CredentialPayload{
Headers: make(map[string]string, len(other.Headers)),
Project: other.Project,
Secret: other.Secret,
}
for k, v := range other.Headers {
rhs.Headers[k] = v
}
// the one thing that makes this function different from a plain
// reflect.DeepEqual(): Headers["X-Roles"] is a comma-separated list
// where ordering does not matter
lhsRoles := p.Headers["X-Roles"]
rhsRoles := rhs.Headers["X-Roles"]
if lhsRoles != "" && rhsRoles != "" {
rhs.Headers["X-Roles"] = sortCommaSeparatedLikeInReference(rhsRoles, lhsRoles)
}
return reflect.DeepEqual(p, rhs)
}
func sortCommaSeparatedLikeInReference(input, reference string) string {
refFieldIndex := make(map[string]int)
for idx, field := range strings.Split(reference, ",") {
refFieldIndex[field] = idx
}
fields := strings.Split(input, ",")
sort.Slice(fields, func(i, j int) bool {
idx1 := refFieldIndex[fields[i]]
idx2 := refFieldIndex[fields[j]]
if idx1 == idx2 {
return fields[i] < fields[j]
}
return idx1 < idx2
})
return strings.Join(fields, ",")
}