1package consul 2 3import ( 4 "errors" 5 "fmt" 6 "time" 7 8 "github.com/hashicorp/go-hclog" 9 "github.com/hashicorp/go-memdb" 10 11 "github.com/hashicorp/consul/acl" 12 "github.com/hashicorp/consul/agent/connect" 13 "github.com/hashicorp/consul/agent/consul/state" 14 "github.com/hashicorp/consul/agent/structs" 15) 16 17var ( 18 // Err strings. net/rpc doesn't have a way to transport typed/rich errors so 19 // we currently rely on sniffing the error string in a few cases where we need 20 // to change client behavior. These are the canonical error strings to use. 21 // Note though that client code can't use `err == consul.Err*` directly since 22 // the error returned by RPC will be a plain error.errorString created by 23 // net/rpc client so will not be the same _instance_ that this package 24 // variable points to. Clients need to compare using `err.Error() == 25 // consul.ErrRateLimited.Error()` which is very sad. Short of replacing our 26 // RPC mechanism it's hard to know how to make that much better though. 27 ErrConnectNotEnabled = errors.New("Connect must be enabled in order to use this endpoint") 28 ErrRateLimited = errors.New("Rate limit reached, try again later") 29 ErrNotPrimaryDatacenter = errors.New("not the primary datacenter") 30 ErrStateReadOnly = errors.New("CA Provider State is read-only") 31) 32 33const ( 34 // csrLimitWait is the maximum time we'll wait for a slot when CSR concurrency 35 // limiting or rate limiting is occurring. It's intentionally short so small 36 // batches of requests can be accommodated when server has capacity (assuming 37 // signing one cert takes much less than this) but failing requests fast when 38 // a thundering herd comes along. 39 csrLimitWait = 500 * time.Millisecond 40) 41 42// ConnectCA manages the Connect CA. 43type ConnectCA struct { 44 // srv is a pointer back to the server. 45 srv *Server 46 47 logger hclog.Logger 48} 49 50// ConfigurationGet returns the configuration for the CA. 51func (s *ConnectCA) ConfigurationGet( 52 args *structs.DCSpecificRequest, 53 reply *structs.CAConfiguration) error { 54 // Exit early if Connect hasn't been enabled. 55 if !s.srv.config.ConnectEnabled { 56 return ErrConnectNotEnabled 57 } 58 59 if done, err := s.srv.ForwardRPC("ConnectCA.ConfigurationGet", args, reply); done { 60 return err 61 } 62 63 // This action requires operator read access. 64 rule, err := s.srv.ResolveToken(args.Token) 65 if err != nil { 66 return err 67 } 68 if rule != nil && rule.OperatorWrite(nil) != acl.Allow { 69 return acl.ErrPermissionDenied 70 } 71 72 state := s.srv.fsm.State() 73 _, config, err := state.CAConfig(nil) 74 if err != nil { 75 return err 76 } 77 *reply = *config 78 79 return nil 80} 81 82// ConfigurationSet updates the configuration for the CA. 83func (s *ConnectCA) ConfigurationSet( 84 args *structs.CARequest, 85 reply *interface{}) error { 86 // Exit early if Connect hasn't been enabled. 87 if !s.srv.config.ConnectEnabled { 88 return ErrConnectNotEnabled 89 } 90 91 if done, err := s.srv.ForwardRPC("ConnectCA.ConfigurationSet", args, reply); done { 92 return err 93 } 94 95 // This action requires operator write access. 96 rule, err := s.srv.ResolveToken(args.Token) 97 if err != nil { 98 return err 99 } 100 if rule != nil && rule.OperatorWrite(nil) != acl.Allow { 101 return acl.ErrPermissionDenied 102 } 103 104 return s.srv.caManager.UpdateConfiguration(args) 105} 106 107// Roots returns the currently trusted root certificates. 108func (s *ConnectCA) Roots( 109 args *structs.DCSpecificRequest, 110 reply *structs.IndexedCARoots) error { 111 // Forward if necessary 112 if done, err := s.srv.ForwardRPC("ConnectCA.Roots", args, reply); done { 113 return err 114 } 115 116 // Exit early if Connect hasn't been enabled. 117 if !s.srv.config.ConnectEnabled { 118 return ErrConnectNotEnabled 119 } 120 121 return s.srv.blockingQuery( 122 &args.QueryOptions, &reply.QueryMeta, 123 func(ws memdb.WatchSet, state *state.Store) error { 124 roots, err := s.srv.getCARoots(ws, state) 125 if err != nil { 126 return err 127 } 128 129 *reply = *roots 130 return nil 131 }, 132 ) 133} 134 135// Sign signs a certificate for a service. 136func (s *ConnectCA) Sign( 137 args *structs.CASignRequest, 138 reply *structs.IssuedCert) error { 139 // Exit early if Connect hasn't been enabled. 140 if !s.srv.config.ConnectEnabled { 141 return ErrConnectNotEnabled 142 } 143 144 if done, err := s.srv.ForwardRPC("ConnectCA.Sign", args, reply); done { 145 return err 146 } 147 148 // Parse the CSR 149 csr, err := connect.ParseCSR(args.CSR) 150 if err != nil { 151 return err 152 } 153 154 // Parse the SPIFFE ID 155 spiffeID, err := connect.ParseCertURI(csr.URIs[0]) 156 if err != nil { 157 return err 158 } 159 160 // Verify that the ACL token provided has permission to act as this service 161 rule, err := s.srv.ResolveToken(args.Token) 162 if err != nil { 163 return err 164 } 165 166 var authzContext acl.AuthorizerContext 167 var entMeta structs.EnterpriseMeta 168 169 serviceID, isService := spiffeID.(*connect.SpiffeIDService) 170 agentID, isAgent := spiffeID.(*connect.SpiffeIDAgent) 171 if !isService && !isAgent { 172 return fmt.Errorf("SPIFFE ID in CSR must be a service or agent ID") 173 } 174 175 if isService { 176 entMeta.Merge(serviceID.GetEnterpriseMeta()) 177 entMeta.FillAuthzContext(&authzContext) 178 if rule != nil && rule.ServiceWrite(serviceID.Service, &authzContext) != acl.Allow { 179 return acl.ErrPermissionDenied 180 } 181 182 // Verify that the DC in the service URI matches us. We might relax this 183 // requirement later but being restrictive for now is safer. 184 if serviceID.Datacenter != s.srv.config.Datacenter { 185 return fmt.Errorf("SPIFFE ID in CSR from a different datacenter: %s, "+ 186 "we are %s", serviceID.Datacenter, s.srv.config.Datacenter) 187 } 188 } else if isAgent { 189 structs.DefaultEnterpriseMeta().FillAuthzContext(&authzContext) 190 if rule != nil && rule.NodeWrite(agentID.Agent, &authzContext) != acl.Allow { 191 return acl.ErrPermissionDenied 192 } 193 } 194 195 cert, err := s.srv.SignCertificate(csr, spiffeID) 196 if err != nil { 197 return err 198 } 199 *reply = *cert 200 return nil 201} 202 203// SignIntermediate signs an intermediate certificate for a remote datacenter. 204func (s *ConnectCA) SignIntermediate( 205 args *structs.CASignRequest, 206 reply *string) error { 207 // Exit early if Connect hasn't been enabled. 208 if !s.srv.config.ConnectEnabled { 209 return ErrConnectNotEnabled 210 } 211 212 if done, err := s.srv.ForwardRPC("ConnectCA.SignIntermediate", args, reply); done { 213 return err 214 } 215 216 // Verify we are allowed to serve this request 217 if s.srv.config.PrimaryDatacenter != s.srv.config.Datacenter { 218 return ErrNotPrimaryDatacenter 219 } 220 221 // This action requires operator write access. 222 rule, err := s.srv.ResolveToken(args.Token) 223 if err != nil { 224 return err 225 } 226 if rule != nil && rule.OperatorWrite(nil) != acl.Allow { 227 return acl.ErrPermissionDenied 228 } 229 230 provider, _ := s.srv.caManager.getCAProvider() 231 if provider == nil { 232 return fmt.Errorf("internal error: CA provider is nil") 233 } 234 235 csr, err := connect.ParseCSR(args.CSR) 236 if err != nil { 237 return err 238 } 239 240 cert, err := provider.SignIntermediate(csr) 241 if err != nil { 242 return err 243 } 244 245 *reply = cert 246 247 return nil 248} 249