1// Copyright 2015 The etcd Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package v2auth 16 17import ( 18 "context" 19 "encoding/json" 20 "path" 21 22 "go.etcd.io/etcd/etcdserver" 23 "go.etcd.io/etcd/etcdserver/api/v2error" 24 "go.etcd.io/etcd/etcdserver/etcdserverpb" 25 26 "go.uber.org/zap" 27) 28 29func (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 if s.lg != nil { 51 s.lg.Warn( 52 "failed to create auth directories", 53 zap.Error(err), 54 ) 55 } else { 56 plog.Errorf("failed to create auth directories in the store (%v)", err) 57 } 58 return err 59 } 60 } 61 ctx, cancel := context.WithTimeout(context.Background(), s.timeout) 62 defer cancel() 63 pe := false 64 rr := etcdserverpb.Request{ 65 Method: "PUT", 66 Path: StorePermsPrefix + "/enabled", 67 Val: "false", 68 PrevExist: &pe, 69 } 70 _, err := s.server.Do(ctx, rr) 71 if err != nil { 72 if e, ok := err.(*v2error.Error); ok { 73 if e.ErrorCode == v2error.EcodeNodeExist { 74 s.ensuredOnce = true 75 return nil 76 } 77 } 78 return err 79 } 80 s.ensuredOnce = true 81 return nil 82} 83 84func (s *store) enableAuth() error { 85 _, err := s.updateResource("/enabled", true) 86 return err 87} 88func (s *store) disableAuth() error { 89 _, err := s.updateResource("/enabled", false) 90 return err 91} 92 93func (s *store) detectAuth() bool { 94 if s.server == nil { 95 return false 96 } 97 value, err := s.requestResource("/enabled", false) 98 if err != nil { 99 if e, ok := err.(*v2error.Error); ok { 100 if e.ErrorCode == v2error.EcodeKeyNotFound { 101 return false 102 } 103 } 104 if s.lg != nil { 105 s.lg.Warn( 106 "failed to detect auth settings", 107 zap.Error(err), 108 ) 109 } else { 110 plog.Errorf("failed to detect auth settings (%s)", err) 111 } 112 return false 113 } 114 115 var u bool 116 err = json.Unmarshal([]byte(*value.Event.Node.Value), &u) 117 if err != nil { 118 if s.lg != nil { 119 s.lg.Warn( 120 "internal bookkeeping value for enabled isn't valid JSON", 121 zap.Error(err), 122 ) 123 } else { 124 plog.Errorf("internal bookkeeping value for enabled isn't valid JSON (%v)", err) 125 } 126 return false 127 } 128 return u 129} 130 131func (s *store) requestResource(res string, quorum bool) (etcdserver.Response, error) { 132 ctx, cancel := context.WithTimeout(context.Background(), s.timeout) 133 defer cancel() 134 p := path.Join(StorePermsPrefix, res) 135 method := "GET" 136 if quorum { 137 method = "QGET" 138 } 139 rr := etcdserverpb.Request{ 140 Method: method, 141 Path: p, 142 Dir: false, // TODO: always false? 143 } 144 return s.server.Do(ctx, rr) 145} 146 147func (s *store) updateResource(res string, value interface{}) (etcdserver.Response, error) { 148 return s.setResource(res, value, true) 149} 150func (s *store) createResource(res string, value interface{}) (etcdserver.Response, error) { 151 return s.setResource(res, value, false) 152} 153func (s *store) setResource(res string, value interface{}, prevexist bool) (etcdserver.Response, error) { 154 err := s.ensureAuthDirectories() 155 if err != nil { 156 return etcdserver.Response{}, err 157 } 158 ctx, cancel := context.WithTimeout(context.Background(), s.timeout) 159 defer cancel() 160 data, err := json.Marshal(value) 161 if err != nil { 162 return etcdserver.Response{}, err 163 } 164 p := path.Join(StorePermsPrefix, res) 165 rr := etcdserverpb.Request{ 166 Method: "PUT", 167 Path: p, 168 Val: string(data), 169 PrevExist: &prevexist, 170 } 171 return s.server.Do(ctx, rr) 172} 173 174func (s *store) deleteResource(res string) error { 175 err := s.ensureAuthDirectories() 176 if err != nil { 177 return err 178 } 179 ctx, cancel := context.WithTimeout(context.Background(), s.timeout) 180 defer cancel() 181 pex := true 182 p := path.Join(StorePermsPrefix, res) 183 _, err = s.server.Do(ctx, etcdserverpb.Request{ 184 Method: "DELETE", 185 Path: p, 186 PrevExist: &pex, 187 }) 188 return err 189} 190