Merge pull request #5991 from gyuho/manual

v2http: client certificate auth via common name
This commit is contained in:
Gyu-Ho Lee 2016-07-21 08:02:17 -07:00 committed by GitHub
commit de638a5e4d
10 changed files with 399 additions and 72 deletions

View File

@ -276,6 +276,42 @@ func TestCtlV2Backup(t *testing.T) { // For https://github.com/coreos/etcd/issue
} }
} }
func TestCtlV2AuthWithCommonName(t *testing.T) {
defer testutil.AfterTest(t)
copiedCfg := configClientTLS
copiedCfg.clientCertAuthEnabled = true
epc := setupEtcdctlTest(t, &copiedCfg, false)
defer func() {
if err := epc.Close(); err != nil {
t.Fatalf("error closing etcd processes (%v)", err)
}
}()
if err := etcdctlRoleAdd(epc, "testrole"); err != nil {
t.Fatalf("failed to add role (%v)", err)
}
if err := etcdctlRoleGrant(epc, "testrole", "--rw", "--path=/foo"); err != nil {
t.Fatalf("failed to grant role (%v)", err)
}
if err := etcdctlUserAdd(epc, "root", "123"); err != nil {
t.Fatalf("failed to add user (%v)", err)
}
if err := etcdctlUserAdd(epc, "Autogenerated CA", "123"); err != nil {
t.Fatalf("failed to add user (%v)", err)
}
if err := etcdctlUserGrant(epc, "Autogenerated CA", "testrole"); err != nil {
t.Fatalf("failed to grant role (%v)", err)
}
if err := etcdctlAuthEnable(epc); err != nil {
t.Fatalf("failed to enable auth (%v)", err)
}
if err := etcdctlSet(epc, "foo", "bar"); err != nil {
t.Fatalf("failed to write (%v)", err)
}
}
func etcdctlPrefixArgs(clus *etcdProcessCluster) []string { func etcdctlPrefixArgs(clus *etcdProcessCluster) []string {
endpoints := "" endpoints := ""
if proxies := clus.proxies(); len(proxies) != 0 { if proxies := clus.proxies(); len(proxies) != 0 {
@ -348,6 +384,13 @@ func etcdctlRoleAdd(clus *etcdProcessCluster, role string) error {
return spawnWithExpect(cmdArgs, role) return spawnWithExpect(cmdArgs, role)
} }
func etcdctlRoleGrant(clus *etcdProcessCluster, role string, perms ...string) error {
cmdArgs := append(etcdctlPrefixArgs(clus), "role", "grant")
cmdArgs = append(cmdArgs, perms...)
cmdArgs = append(cmdArgs, role)
return spawnWithExpect(cmdArgs, role)
}
func etcdctlRoleList(clus *etcdProcessCluster, expectedRole string) error { func etcdctlRoleList(clus *etcdProcessCluster, expectedRole string) error {
cmdArgs := append(etcdctlPrefixArgs(clus), "role", "list") cmdArgs := append(etcdctlPrefixArgs(clus), "role", "list")
return spawnWithExpect(cmdArgs, expectedRole) return spawnWithExpect(cmdArgs, expectedRole)

View File

@ -150,6 +150,7 @@ type etcdProcessClusterConfig struct {
snapCount int // default is 10000 snapCount int // default is 10000
clientTLS clientConnType clientTLS clientConnType
clientCertAuthEnabled bool
isPeerTLS bool isPeerTLS bool
isPeerAutoTLS bool isPeerAutoTLS bool
isClientAutoTLS bool isClientAutoTLS bool
@ -325,6 +326,10 @@ func (cfg *etcdProcessClusterConfig) tlsArgs() (args []string) {
"--ca-file", caPath, "--ca-file", caPath,
} }
args = append(args, tlsClientArgs...) args = append(args, tlsClientArgs...)
if cfg.clientCertAuthEnabled {
args = append(args, "--client-cert-auth")
}
} }
} }

View File

@ -116,6 +116,7 @@ func StartEtcd(inCfg *Config) (e *Etcd, err error) {
QuotaBackendBytes: cfg.QuotaBackendBytes, QuotaBackendBytes: cfg.QuotaBackendBytes,
StrictReconfigCheck: cfg.StrictReconfigCheck, StrictReconfigCheck: cfg.StrictReconfigCheck,
EnablePprof: cfg.EnablePprof, EnablePprof: cfg.EnablePprof,
ClientCertAuthEnabled: cfg.ClientTLSInfo.ClientCertAuth,
} }
if e.Server, err = etcdserver.NewServer(srvcfg); err != nil { if e.Server, err = etcdserver.NewServer(srvcfg); err != nil {

View File

@ -70,6 +70,7 @@ func NewClientHandler(server *etcdserver.EtcdServer, timeout time.Duration) http
cluster: server.Cluster(), cluster: server.Cluster(),
timer: server, timer: server,
timeout: timeout, timeout: timeout,
clientCertAuthEnabled: server.Cfg.ClientCertAuthEnabled,
} }
sh := &statsHandler{ sh := &statsHandler{
@ -82,6 +83,7 @@ func NewClientHandler(server *etcdserver.EtcdServer, timeout time.Duration) http
cluster: server.Cluster(), cluster: server.Cluster(),
timeout: timeout, timeout: timeout,
clock: clockwork.NewRealClock(), clock: clockwork.NewRealClock(),
clientCertAuthEnabled: server.Cfg.ClientCertAuthEnabled,
} }
dmh := &deprecatedMachinesHandler{ dmh := &deprecatedMachinesHandler{
@ -91,6 +93,7 @@ func NewClientHandler(server *etcdserver.EtcdServer, timeout time.Duration) http
sech := &authHandler{ sech := &authHandler{
sec: sec, sec: sec,
cluster: server.Cluster(), cluster: server.Cluster(),
clientCertAuthEnabled: server.Cfg.ClientCertAuthEnabled,
} }
mux := http.NewServeMux() mux := http.NewServeMux()
@ -137,6 +140,7 @@ type keysHandler struct {
cluster api.Cluster cluster api.Cluster
timer etcdserver.RaftTimer timer etcdserver.RaftTimer
timeout time.Duration timeout time.Duration
clientCertAuthEnabled bool
} }
func (h *keysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *keysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@ -156,7 +160,7 @@ func (h *keysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
// The path must be valid at this point (we've parsed the request successfully). // The path must be valid at this point (we've parsed the request successfully).
if !hasKeyPrefixAccess(h.sec, r, r.URL.Path[len(keysPrefix):], rr.Recursive) { if !hasKeyPrefixAccess(h.sec, r, r.URL.Path[len(keysPrefix):], rr.Recursive, h.clientCertAuthEnabled) {
writeKeyNoAuth(w) writeKeyNoAuth(w)
return return
} }
@ -204,13 +208,14 @@ type membersHandler struct {
cluster api.Cluster cluster api.Cluster
timeout time.Duration timeout time.Duration
clock clockwork.Clock clock clockwork.Clock
clientCertAuthEnabled bool
} }
func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !allowMethod(w, r.Method, "GET", "POST", "DELETE", "PUT") { if !allowMethod(w, r.Method, "GET", "POST", "DELETE", "PUT") {
return return
} }
if !hasWriteRootAccess(h.sec, r) { if !hasWriteRootAccess(h.sec, r, h.clientCertAuthEnabled) {
writeNoAuth(w, r) writeNoAuth(w, r)
return return
} }

View File

@ -28,16 +28,54 @@ import (
type authHandler struct { type authHandler struct {
sec auth.Store sec auth.Store
cluster api.Cluster cluster api.Cluster
clientCertAuthEnabled bool
} }
func hasWriteRootAccess(sec auth.Store, r *http.Request) bool { func hasWriteRootAccess(sec auth.Store, r *http.Request, clientCertAuthEnabled bool) bool {
if r.Method == "GET" || r.Method == "HEAD" { if r.Method == "GET" || r.Method == "HEAD" {
return true return true
} }
return hasRootAccess(sec, r) return hasRootAccess(sec, r, clientCertAuthEnabled)
} }
func hasRootAccess(sec auth.Store, r *http.Request) bool { func userFromBasicAuth(sec auth.Store, r *http.Request) *auth.User {
username, password, ok := r.BasicAuth()
if !ok {
plog.Warningf("auth: malformed basic auth encoding")
return nil
}
user, err := sec.GetUser(username)
if err != nil {
return nil
}
ok = sec.CheckPassword(user, password)
if !ok {
plog.Warningf("auth: incorrect password for user: %s", username)
return nil
}
return &user
}
func userFromClientCertificate(sec auth.Store, r *http.Request) *auth.User {
if r.TLS == nil {
return nil
}
for _, chains := range r.TLS.VerifiedChains {
for _, chain := range chains {
plog.Debugf("auth: found common name %s.\n", chain.Subject.CommonName)
user, err := sec.GetUser(chain.Subject.CommonName)
if err == nil {
plog.Debugf("auth: authenticated user %s by cert common name.", user.User)
return &user
}
}
}
return nil
}
func hasRootAccess(sec auth.Store, r *http.Request, clientCertAuthEnabled bool) bool {
if sec == nil { if sec == nil {
// No store means no auth available, eg, tests. // No store means no auth available, eg, tests.
return true return true
@ -45,30 +83,30 @@ func hasRootAccess(sec auth.Store, r *http.Request) bool {
if !sec.AuthEnabled() { if !sec.AuthEnabled() {
return true return true
} }
username, password, ok := r.BasicAuth()
if !ok { var rootUser *auth.User
if r.Header.Get("Authorization") == "" && clientCertAuthEnabled {
rootUser = userFromClientCertificate(sec, r)
if rootUser == nil {
return false return false
} }
rootUser, err := sec.GetUser(username) } else {
if err != nil { rootUser = userFromBasicAuth(sec, r)
if rootUser == nil {
return false return false
} }
}
ok = sec.CheckPassword(rootUser, password)
if !ok {
plog.Warningf("auth: wrong password for user %s", username)
return false
}
for _, role := range rootUser.Roles { for _, role := range rootUser.Roles {
if role == auth.RootRoleName { if role == auth.RootRoleName {
return true return true
} }
} }
plog.Warningf("auth: user %s does not have the %s role for resource %s.", username, auth.RootRoleName, r.URL.Path) plog.Warningf("auth: user %s does not have the %s role for resource %s.", rootUser.User, auth.RootRoleName, r.URL.Path)
return false return false
} }
func hasKeyPrefixAccess(sec auth.Store, r *http.Request, key string, recursive bool) bool { func hasKeyPrefixAccess(sec auth.Store, r *http.Request, key string, recursive, clientCertAuthEnabled bool) bool {
if sec == nil { if sec == nil {
// No store means no auth available, eg, tests. // No store means no auth available, eg, tests.
return true return true
@ -76,25 +114,21 @@ func hasKeyPrefixAccess(sec auth.Store, r *http.Request, key string, recursive b
if !sec.AuthEnabled() { if !sec.AuthEnabled() {
return true return true
} }
if r.Header.Get("Authorization") == "" {
var user *auth.User
if r.Header.Get("Authorization") == "" && clientCertAuthEnabled {
user = userFromClientCertificate(sec, r)
if user == nil {
plog.Warningf("auth: no authorization provided, checking guest access") plog.Warningf("auth: no authorization provided, checking guest access")
return hasGuestAccess(sec, r, key) return hasGuestAccess(sec, r, key)
} }
username, password, ok := r.BasicAuth() } else {
if !ok { user = userFromBasicAuth(sec, r)
plog.Warningf("auth: malformed basic auth encoding") if user == nil {
return false return false
} }
user, err := sec.GetUser(username)
if err != nil {
plog.Warningf("auth: no such user: %s.", username)
return false
}
authAsUser := sec.CheckPassword(user, password)
if !authAsUser {
plog.Warningf("auth: incorrect password for user: %s.", username)
return false
} }
writeAccess := r.Method != "GET" && r.Method != "HEAD" writeAccess := r.Method != "GET" && r.Method != "HEAD"
for _, roleName := range user.Roles { for _, roleName := range user.Roles {
role, err := sec.GetRole(roleName) role, err := sec.GetRole(roleName)
@ -109,7 +143,7 @@ func hasKeyPrefixAccess(sec auth.Store, r *http.Request, key string, recursive b
return true return true
} }
} }
plog.Warningf("auth: invalid access for user %s on key %s.", username, key) plog.Warningf("auth: invalid access for user %s on key %s.", user.User, key)
return false return false
} }
@ -145,7 +179,7 @@ func (sh *authHandler) baseRoles(w http.ResponseWriter, r *http.Request) {
if !allowMethod(w, r.Method, "GET") { if !allowMethod(w, r.Method, "GET") {
return return
} }
if !hasRootAccess(sh.sec, r) { if !hasRootAccess(sh.sec, r, sh.clientCertAuthEnabled) {
writeNoAuth(w, r) writeNoAuth(w, r)
return return
} }
@ -209,7 +243,7 @@ func (sh *authHandler) forRole(w http.ResponseWriter, r *http.Request, role stri
if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") { if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") {
return return
} }
if !hasRootAccess(sh.sec, r) { if !hasRootAccess(sh.sec, r, sh.clientCertAuthEnabled) {
writeNoAuth(w, r) writeNoAuth(w, r)
return return
} }
@ -293,7 +327,7 @@ func (sh *authHandler) baseUsers(w http.ResponseWriter, r *http.Request) {
if !allowMethod(w, r.Method, "GET") { if !allowMethod(w, r.Method, "GET") {
return return
} }
if !hasRootAccess(sh.sec, r) { if !hasRootAccess(sh.sec, r, sh.clientCertAuthEnabled) {
writeNoAuth(w, r) writeNoAuth(w, r)
return return
} }
@ -365,7 +399,7 @@ func (sh *authHandler) forUser(w http.ResponseWriter, r *http.Request, user stri
if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") { if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") {
return return
} }
if !hasRootAccess(sh.sec, r) { if !hasRootAccess(sh.sec, r, sh.clientCertAuthEnabled) {
writeNoAuth(w, r) writeNoAuth(w, r)
return return
} }
@ -478,7 +512,7 @@ func (sh *authHandler) enableDisable(w http.ResponseWriter, r *http.Request) {
if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") { if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") {
return return
} }
if !hasWriteRootAccess(sh.sec, r) { if !hasWriteRootAccess(sh.sec, r, sh.clientCertAuthEnabled) {
writeNoAuth(w, r) writeNoAuth(w, r)
return return
} }

View File

@ -15,9 +15,13 @@
package v2http package v2http
import ( import (
"crypto/tls"
"crypto/x509"
"encoding/json" "encoding/json"
"encoding/pem"
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
@ -440,6 +444,32 @@ func mustAuthRequest(method, username, password string) *http.Request {
return req return req
} }
func unauthedRequest(method string) *http.Request {
req, err := http.NewRequest(method, "path", strings.NewReader(""))
if err != nil {
panic("Cannot make request: " + err.Error())
}
return req
}
func tlsAuthedRequest(req *http.Request, certname string) *http.Request {
bytes, err := ioutil.ReadFile(fmt.Sprintf("testdata/%s.pem", certname))
if err != nil {
panic(err)
}
block, _ := pem.Decode(bytes)
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
panic(err)
}
req.TLS = &tls.ConnectionState{
VerifiedChains: [][]*x509.Certificate{{cert}},
}
return req
}
func TestPrefixAccess(t *testing.T) { func TestPrefixAccess(t *testing.T) {
var table = []struct { var table = []struct {
key string key string
@ -690,14 +720,161 @@ func TestPrefixAccess(t *testing.T) {
} }
for i, tt := range table { for i, tt := range table {
if tt.hasRoot != hasRootAccess(tt.store, tt.req) { if tt.hasRoot != hasRootAccess(tt.store, tt.req, true) {
t.Errorf("#%d: hasRoot doesn't match (expected %v)", i, tt.hasRoot) t.Errorf("#%d: hasRoot doesn't match (expected %v)", i, tt.hasRoot)
} }
if tt.hasKeyPrefixAccess != hasKeyPrefixAccess(tt.store, tt.req, tt.key, false) { if tt.hasKeyPrefixAccess != hasKeyPrefixAccess(tt.store, tt.req, tt.key, false, true) {
t.Errorf("#%d: hasKeyPrefixAccess doesn't match (expected %v)", i, tt.hasRoot) t.Errorf("#%d: hasKeyPrefixAccess doesn't match (expected %v)", i, tt.hasRoot)
} }
if tt.hasRecursiveAccess != hasKeyPrefixAccess(tt.store, tt.req, tt.key, true) { if tt.hasRecursiveAccess != hasKeyPrefixAccess(tt.store, tt.req, tt.key, true, true) {
t.Errorf("#%d: hasRecursiveAccess doesn't match (expected %v)", i, tt.hasRoot) t.Errorf("#%d: hasRecursiveAccess doesn't match (expected %v)", i, tt.hasRoot)
} }
} }
} }
func TestUserFromClientCertificate(t *testing.T) {
witherror := &mockAuthStore{
users: map[string]*auth.User{
"user": {
User: "user",
Roles: []string{"root"},
Password: "password",
},
"basicauth": {
User: "basicauth",
Roles: []string{"root"},
Password: "password",
},
},
roles: map[string]*auth.Role{
"root": {
Role: "root",
},
},
err: errors.New(""),
}
noerror := &mockAuthStore{
users: map[string]*auth.User{
"user": {
User: "user",
Roles: []string{"root"},
Password: "password",
},
"basicauth": {
User: "basicauth",
Roles: []string{"root"},
Password: "password",
},
},
roles: map[string]*auth.Role{
"root": {
Role: "root",
},
},
}
var table = []struct {
req *http.Request
userExists bool
store auth.Store
username string
}{
{
// non tls request
req: unauthedRequest("GET"),
userExists: false,
store: witherror,
},
{
// cert with cn of existing user
req: tlsAuthedRequest(unauthedRequest("GET"), "user"),
userExists: true,
username: "user",
store: noerror,
},
{
// cert with cn of non-existing user
req: tlsAuthedRequest(unauthedRequest("GET"), "otheruser"),
userExists: false,
store: witherror,
},
}
for i, tt := range table {
user := userFromClientCertificate(tt.store, tt.req)
userExists := user != nil
if tt.userExists != userExists {
t.Errorf("#%d: userFromClientCertificate doesn't match (expected %v)", i, tt.userExists)
}
if user != nil && (tt.username != user.User) {
t.Errorf("#%d: userFromClientCertificate username doesn't match (expected %s, got %s)", i, tt.username, user.User)
}
}
}
func TestUserFromBasicAuth(t *testing.T) {
sec := &mockAuthStore{
users: map[string]*auth.User{
"user": {
User: "user",
Roles: []string{"root"},
Password: "password",
},
},
roles: map[string]*auth.Role{
"root": {
Role: "root",
},
},
}
var table = []struct {
username string
req *http.Request
userExists bool
}{
{
// valid user, valid pass
username: "user",
req: mustAuthRequest("GET", "user", "password"),
userExists: true,
},
{
// valid user, bad pass
username: "user",
req: mustAuthRequest("GET", "user", "badpass"),
userExists: false,
},
{
// valid user, no pass
username: "user",
req: mustAuthRequest("GET", "user", ""),
userExists: false,
},
{
// missing user
username: "missing",
req: mustAuthRequest("GET", "missing", "badpass"),
userExists: false,
},
{
// no basic auth
req: unauthedRequest("GET"),
userExists: false,
},
}
for i, tt := range table {
user := userFromBasicAuth(sec, tt.req)
userExists := user != nil
if tt.userExists != userExists {
t.Errorf("#%d: userFromBasicAuth doesn't match (expected %v)", i, tt.userExists)
}
if user != nil && (tt.username != user.User) {
t.Errorf("#%d: userFromBasicAuth username doesn't match (expected %s, got %s)", i, tt.username, user.User)
}
}
}

19
etcdserver/api/v2http/testdata/ca.pem vendored Normal file
View File

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDEjCCAfqgAwIBAgIIYpX+8HgWGfkwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKZXRjZCB0ZXN0czAeFw0xNTExMjQwMzA1MDBaFw0yMDExMjIwMzA1MDBaMBUx
EzARBgNVBAMTCmV0Y2QgdGVzdHMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDa9PkwEwiBD8mB+VIKz5r5gRHnNF4Icj6T6R/RsdatecQe6vU0EU4FXtKZ
drWnCGlATyrQooqHpb+rDc7CUt3mXrIxrNkcGTMaesF7P0GWxVkyOGSjJMxGBv3e
bAZknBe4eLMi68L1aT/uYmxcp/B3L2mfdFtc1Gd6mYJpNm1PgilRyIrO0mY5ysIX
4WHCa3yudAv8HrFbQcw7l7OyKA6uSWg6h07lE3d5jw5YOly+hz0iaRtzhb4tJrYD
Lm1tehb0nnoLuW6yYblRSoyBVDT50MFVlyvW40Po5WkOXw/wnsnyxWRR4yqU23wq
quQU0HXJEBLFnT+KbLOQ0EAE35vXAgMBAAGjZjBkMA4GA1UdDwEB/wQEAwIBBjAS
BgNVHRMBAf8ECDAGAQH/AgECMB0GA1UdDgQWBBSbUCGB95ochDrbEZlzGGYuA7xu
xjAfBgNVHSMEGDAWgBSbUCGB95ochDrbEZlzGGYuA7xuxjANBgkqhkiG9w0BAQsF
AAOCAQEAardO/SGCu7Snz3YRBUinzpZEUFTFend+FJtBkxBXCao1RvTXg8PBMkza
LUsaR4mLsGoXLIbNCoIinvVG0QULYCZe11N3l1L0G2g5uhEM4MfJ2rwrMD0o17i+
nwNRRE3tfKAlWhYQg+4ye36kQVxASPniHjdQgjKYUFTNXdyG6DzuAclaVte9iVw6
cWl61fB2CZya3+uMtih8t/Kgl2KbMO2PvNByfnDjKmW+v58qHbXyoJZqnpvDn14+
p2Ox+AvvxYiEiUIvFdWy101QB7NJMCtdwq6oG6OvIOgXzLgitTFSq4kfWDfupQjW
iFoQ+vWmYhK5ld0nBaiz+JmHuemK7A==
-----END CERTIFICATE-----

View File

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDOTCCAiGgAwIBAgIINYpsso1f3SswDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKZXRjZCB0ZXN0czAeFw0xNTExMjQwMzA4MDBaFw0xNjExMjMwMzA4MDBaMBQx
EjAQBgNVBAMTCW90aGVydXNlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAPOAUa5GblwIjHTEnox2c/Am9jV1TMvzBuVXxnp2UnNHMNwstAooFrEs/Z+d
ft5AOsooP6zVuM3eBQa4i9huJbVNDfPU2H94yA89jYfJYUgo7C838V6NjGsCCptQ
WzkKPNlDbT9xA/7XpIUJ2WltuYDRrjWq8pXQONqTjcg5n4l0JO8xdHJHRUkFQ76F
1npXeLndgGaP11lqzpYlglEGi5URhzAT1xxQ0hLSe8WNmiCxxkq++C8Gx4sPg9mX
M94aoJDzZSnoaqDxckbP/7Q0ZKe/fVdCFkd5+jqT4Mt7hwmz9jTCHcVnAz4EKI+t
rbWgbCfMK6013GotXz7InStVe+MCAwEAAaOBjTCBijAOBgNVHQ8BAf8EBAMCBaAw
HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYD
VR0OBBYEFFwMmf+pnaejmri6y1T+lfU+MBq/MB8GA1UdIwQYMBaAFJtQIYH3mhyE
OtsRmXMYZi4DvG7GMAsGA1UdEQQEMAKCADANBgkqhkiG9w0BAQsFAAOCAQEACOn6
mec29MTMGPt/EPOmSyhvTKSwH+5YWjCbyUFeoB8puxrJlIphK4mvT+sXp2wzno89
FVCliO/rJurdErKvyOjlK1QrVGPYIt7Wz9ssAfvlwCyBM8PqgEG8dJN9aAkf2h4r
Ye+hBh1y6Nnataf7lxe9mqAOvD/7wVIgzjCnMD1q5QSY2Mln3HwVQXtbZFbY363Z
X9Fk3PUpjJSX9jbEz9kIlT8AJAdxl6GB8Z9B8PrA8qf4Bhk15ICRHxb67EhDrGWV
8q7ArU2XBqs/+GWpUIMoGKNZv+K+/SksZK1KnzaUvApUCJzt+ac+p8HOgMdvDRgr
GfVVJqcZgyEmeczy0A==
-----END CERTIFICATE-----

20
etcdserver/api/v2http/testdata/user.pem vendored Normal file
View File

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDNDCCAhygAwIBAgIIcQ0DAfgevocwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKZXRjZCB0ZXN0czAeFw0xNTExMjQwMzA4MDBaFw0xNjExMjMwMzA4MDBaMA8x
DTALBgNVBAMTBHVzZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD0
+3Lm1SmUJJLufaFTYz+e5qyQEshNRyeAhXIeZ1aw+yBjslXGZQ3/uGOwnOnGqUeA
Nidc9ty4NsK6RVppHlezUrBnpl4hws8vHWFKZpU2R6kKL8EYLmg+iVqEBj7XqfAp
8bJqqZI3KOqLXpRH55mA69KP7VEK9ngTVR/tERSrUPT8jcjwbvhSOqD8Qk07BUDR
6RpDr94Mnaf+fMGG36Sh7iUl+i4Oh6FFar+7+b0+5Bhs2/6uVsK4A1Z3jqqfSQH8
q8Wf5h9Ka4aqGSw4ia5G3Uw7Jsl2aDgpJ7uwJo1k8SclbMYnYdhZuo+U+esY/Fai
YdbjG+AroZ+y9TB8bMlHAgMBAAGjgY0wgYowDgYDVR0PAQH/BAQDAgWgMB0GA1Ud
JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW
BBRuTt0lJIVKYaz76aSxl/MQOLRwfDAfBgNVHSMEGDAWgBSbUCGB95ochDrbEZlz
GGYuA7xuxjALBgNVHREEBDACggAwDQYJKoZIhvcNAQELBQADggEBABLRWZm+Lgjs
c5qDXbgOJW2pR630syY8ixR9c6HvzPVJim8mFioMX+xrlbOC6BmOUlOb9j83bTKn
aOg/0xlpxNbd8QYzgRxZmHZLULPdiNeeRvIzsrzrH88+inrmZhRXRVcHjdO6CG6t
hCdDdRiNU6GkF7dPna0xNcEOKe2wUfzd1ZtKOqzi1w+fKjSeMplZomeWgP4WRvkh
JJ/0ujlMMckgyTxRh8EEaJ35OnpXX7EdipoWhOMmiUnlPqye2icC8Y+CMdZsrod6
nkoEQnXDCLf/Iv0qj7B9iKbxn7t3QDVxY4UILUReDuD8yrGULlGOl//aY/T3pkZ6
R5trduZhI3o=
-----END CERTIFICATE-----

View File

@ -56,6 +56,9 @@ type ServerConfig struct {
StrictReconfigCheck bool StrictReconfigCheck bool
EnablePprof bool EnablePprof bool
// ClientCertAuthEnabled is true when cert has been signed by the client CA.
ClientCertAuthEnabled bool
} }
// VerifyBootstrap sanity-checks the initial config for bootstrap case // VerifyBootstrap sanity-checks the initial config for bootstrap case