1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package v2auth
16
17 import (
18 "context"
19 "encoding/json"
20 "path"
21
22 "go.etcd.io/etcd/api/v3/etcdserverpb"
23 "go.etcd.io/etcd/server/v3/etcdserver"
24 "go.etcd.io/etcd/server/v3/etcdserver/api/v2error"
25
26 "go.uber.org/zap"
27 )
28
29 func (s *store) ensureAuthDirectories() error {
30 if s.ensuredOnce {
31 return nil
32 }
33 for _, res := range []string{StorePermsPrefix, StorePermsPrefix + "/users/", StorePermsPrefix + "/roles/"} {
34 ctx, cancel := context.WithTimeout(context.Background(), s.timeout)
35 pe := false
36 rr := etcdserverpb.Request{
37 Method: "PUT",
38 Path: res,
39 Dir: true,
40 PrevExist: &pe,
41 }
42 _, err := s.server.Do(ctx, rr)
43 cancel()
44 if err != nil {
45 if e, ok := err.(*v2error.Error); ok {
46 if e.ErrorCode == v2error.EcodeNodeExist {
47 continue
48 }
49 }
50 s.lg.Warn(
51 "failed to create auth directories",
52 zap.Error(err),
53 )
54 return err
55 }
56 }
57 ctx, cancel := context.WithTimeout(context.Background(), s.timeout)
58 defer cancel()
59 pe := false
60 rr := etcdserverpb.Request{
61 Method: "PUT",
62 Path: StorePermsPrefix + "/enabled",
63 Val: "false",
64 PrevExist: &pe,
65 }
66 _, err := s.server.Do(ctx, rr)
67 if err != nil {
68 if e, ok := err.(*v2error.Error); ok {
69 if e.ErrorCode == v2error.EcodeNodeExist {
70 s.ensuredOnce = true
71 return nil
72 }
73 }
74 return err
75 }
76 s.ensuredOnce = true
77 return nil
78 }
79
80 func (s *store) enableAuth() error {
81 _, err := s.updateResource("/enabled", true)
82 return err
83 }
84 func (s *store) disableAuth() error {
85 _, err := s.updateResource("/enabled", false)
86 return err
87 }
88
89 func (s *store) detectAuth() bool {
90 if s.server == nil {
91 return false
92 }
93 value, err := s.requestResource("/enabled", false)
94 if err != nil {
95 if e, ok := err.(*v2error.Error); ok {
96 if e.ErrorCode == v2error.EcodeKeyNotFound {
97 return false
98 }
99 }
100 s.lg.Warn(
101 "failed to detect auth settings",
102 zap.Error(err),
103 )
104 return false
105 }
106
107 var u bool
108 err = json.Unmarshal([]byte(*value.Event.Node.Value), &u)
109 if err != nil {
110 s.lg.Warn(
111 "internal bookkeeping value for enabled isn't valid JSON",
112 zap.Error(err),
113 )
114 return false
115 }
116 return u
117 }
118
119 func (s *store) requestResource(res string, quorum bool) (etcdserver.Response, error) {
120 ctx, cancel := context.WithTimeout(context.Background(), s.timeout)
121 defer cancel()
122 p := path.Join(StorePermsPrefix, res)
123 method := "GET"
124 if quorum {
125 method = "QGET"
126 }
127 rr := etcdserverpb.Request{
128 Method: method,
129 Path: p,
130 Dir: false,
131 }
132 return s.server.Do(ctx, rr)
133 }
134
135 func (s *store) updateResource(res string, value interface{}) (etcdserver.Response, error) {
136 return s.setResource(res, value, true)
137 }
138 func (s *store) createResource(res string, value interface{}) (etcdserver.Response, error) {
139 return s.setResource(res, value, false)
140 }
141 func (s *store) setResource(res string, value interface{}, prevexist bool) (etcdserver.Response, error) {
142 err := s.ensureAuthDirectories()
143 if err != nil {
144 return etcdserver.Response{}, err
145 }
146 ctx, cancel := context.WithTimeout(context.Background(), s.timeout)
147 defer cancel()
148 data, err := json.Marshal(value)
149 if err != nil {
150 return etcdserver.Response{}, err
151 }
152 p := path.Join(StorePermsPrefix, res)
153 rr := etcdserverpb.Request{
154 Method: "PUT",
155 Path: p,
156 Val: string(data),
157 PrevExist: &prevexist,
158 }
159 return s.server.Do(ctx, rr)
160 }
161
162 func (s *store) deleteResource(res string) error {
163 err := s.ensureAuthDirectories()
164 if err != nil {
165 return err
166 }
167 ctx, cancel := context.WithTimeout(context.Background(), s.timeout)
168 defer cancel()
169 pex := true
170 p := path.Join(StorePermsPrefix, res)
171 _, err = s.server.Do(ctx, etcdserverpb.Request{
172 Method: "DELETE",
173 Path: p,
174 PrevExist: &pex,
175 })
176 return err
177 }
178
View as plain text