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