hub.fastgit.org/hashicorp/consul.git@v1.4.5/agent/consul/intention_endpoint.go (about) 1 package consul 2 3 import ( 4 "errors" 5 "fmt" 6 "time" 7 8 "github.com/armon/go-metrics" 9 "github.com/hashicorp/consul/acl" 10 "github.com/hashicorp/consul/agent/connect" 11 "github.com/hashicorp/consul/agent/consul/state" 12 "github.com/hashicorp/consul/agent/structs" 13 "github.com/hashicorp/go-memdb" 14 "github.com/hashicorp/go-uuid" 15 ) 16 17 var ( 18 // ErrIntentionNotFound is returned if the intention lookup failed. 19 ErrIntentionNotFound = errors.New("Intention not found") 20 ) 21 22 // Intention manages the Connect intentions. 23 type Intention struct { 24 // srv is a pointer back to the server. 25 srv *Server 26 } 27 28 // Apply creates or updates an intention in the data store. 29 func (s *Intention) Apply( 30 args *structs.IntentionRequest, 31 reply *string) error { 32 33 // Forward this request to the primary DC if we're a secondary that's replicating intentions. 34 if s.srv.intentionReplicationEnabled() { 35 args.Datacenter = s.srv.config.PrimaryDatacenter 36 } 37 38 if done, err := s.srv.forward("Intention.Apply", args, args, reply); done { 39 return err 40 } 41 defer metrics.MeasureSince([]string{"consul", "intention", "apply"}, time.Now()) 42 defer metrics.MeasureSince([]string{"intention", "apply"}, time.Now()) 43 44 // Always set a non-nil intention to avoid nil-access below 45 if args.Intention == nil { 46 args.Intention = &structs.Intention{} 47 } 48 49 // If no ID is provided, generate a new ID. This must be done prior to 50 // appending to the Raft log, because the ID is not deterministic. Once 51 // the entry is in the log, the state update MUST be deterministic or 52 // the followers will not converge. 53 if args.Op == structs.IntentionOpCreate { 54 if args.Intention.ID != "" { 55 return fmt.Errorf("ID must be empty when creating a new intention") 56 } 57 58 state := s.srv.fsm.State() 59 for { 60 var err error 61 args.Intention.ID, err = uuid.GenerateUUID() 62 if err != nil { 63 s.srv.logger.Printf("[ERR] consul.intention: UUID generation failed: %v", err) 64 return err 65 } 66 67 _, ixn, err := state.IntentionGet(nil, args.Intention.ID) 68 if err != nil { 69 s.srv.logger.Printf("[ERR] consul.intention: intention lookup failed: %v", err) 70 return err 71 } 72 if ixn == nil { 73 break 74 } 75 } 76 77 // Set the created at 78 args.Intention.CreatedAt = time.Now().UTC() 79 } 80 *reply = args.Intention.ID 81 82 // Get the ACL token for the request for the checks below. 83 rule, err := s.srv.ResolveToken(args.Token) 84 if err != nil { 85 return err 86 } 87 88 // Perform the ACL check 89 if prefix, ok := args.Intention.GetACLPrefix(); ok { 90 if rule != nil && !rule.IntentionWrite(prefix) { 91 s.srv.logger.Printf("[WARN] consul.intention: Operation on intention '%s' denied due to ACLs", args.Intention.ID) 92 return acl.ErrPermissionDenied 93 } 94 } 95 96 // If this is not a create, then we have to verify the ID. 97 if args.Op != structs.IntentionOpCreate { 98 state := s.srv.fsm.State() 99 _, ixn, err := state.IntentionGet(nil, args.Intention.ID) 100 if err != nil { 101 return fmt.Errorf("Intention lookup failed: %v", err) 102 } 103 if ixn == nil { 104 return fmt.Errorf("Cannot modify non-existent intention: '%s'", args.Intention.ID) 105 } 106 107 // Perform the ACL check that we have write to the old prefix too, 108 // which must be true to perform any rename. 109 if prefix, ok := ixn.GetACLPrefix(); ok { 110 if rule != nil && !rule.IntentionWrite(prefix) { 111 s.srv.logger.Printf("[WARN] consul.intention: Operation on intention '%s' denied due to ACLs", args.Intention.ID) 112 return acl.ErrPermissionDenied 113 } 114 } 115 } 116 117 // We always update the updatedat field. This has no effect for deletion. 118 args.Intention.UpdatedAt = time.Now().UTC() 119 120 // Default source type 121 if args.Intention.SourceType == "" { 122 args.Intention.SourceType = structs.IntentionSourceConsul 123 } 124 125 // Until we support namespaces, we force all namespaces to be default 126 if args.Intention.SourceNS == "" { 127 args.Intention.SourceNS = structs.IntentionDefaultNamespace 128 } 129 if args.Intention.DestinationNS == "" { 130 args.Intention.DestinationNS = structs.IntentionDefaultNamespace 131 } 132 133 // Validate. We do not validate on delete since it is valid to only 134 // send an ID in that case. 135 if args.Op != structs.IntentionOpDelete { 136 // Set the precedence 137 args.Intention.UpdatePrecedence() 138 139 if err := args.Intention.Validate(); err != nil { 140 return err 141 } 142 } 143 144 // Commit 145 resp, err := s.srv.raftApply(structs.IntentionRequestType, args) 146 if err != nil { 147 s.srv.logger.Printf("[ERR] consul.intention: Apply failed %v", err) 148 return err 149 } 150 if respErr, ok := resp.(error); ok { 151 return respErr 152 } 153 154 return nil 155 } 156 157 // Get returns a single intention by ID. 158 func (s *Intention) Get( 159 args *structs.IntentionQueryRequest, 160 reply *structs.IndexedIntentions) error { 161 // Forward if necessary 162 if done, err := s.srv.forward("Intention.Get", args, args, reply); done { 163 return err 164 } 165 166 return s.srv.blockingQuery( 167 &args.QueryOptions, 168 &reply.QueryMeta, 169 func(ws memdb.WatchSet, state *state.Store) error { 170 index, ixn, err := state.IntentionGet(ws, args.IntentionID) 171 if err != nil { 172 return err 173 } 174 if ixn == nil { 175 return ErrIntentionNotFound 176 } 177 178 reply.Index = index 179 reply.Intentions = structs.Intentions{ixn} 180 181 // Filter 182 if err := s.srv.filterACL(args.Token, reply); err != nil { 183 return err 184 } 185 186 // If ACLs prevented any responses, error 187 if len(reply.Intentions) == 0 { 188 s.srv.logger.Printf("[WARN] consul.intention: Request to get intention '%s' denied due to ACLs", args.IntentionID) 189 return acl.ErrPermissionDenied 190 } 191 192 return nil 193 }, 194 ) 195 } 196 197 // List returns all the intentions. 198 func (s *Intention) List( 199 args *structs.DCSpecificRequest, 200 reply *structs.IndexedIntentions) error { 201 // Forward if necessary 202 if done, err := s.srv.forward("Intention.List", args, args, reply); done { 203 return err 204 } 205 206 return s.srv.blockingQuery( 207 &args.QueryOptions, &reply.QueryMeta, 208 func(ws memdb.WatchSet, state *state.Store) error { 209 index, ixns, err := state.Intentions(ws) 210 if err != nil { 211 return err 212 } 213 214 reply.Index, reply.Intentions = index, ixns 215 if reply.Intentions == nil { 216 reply.Intentions = make(structs.Intentions, 0) 217 } 218 219 return s.srv.filterACL(args.Token, reply) 220 }, 221 ) 222 } 223 224 // Match returns the set of intentions that match the given source/destination. 225 func (s *Intention) Match( 226 args *structs.IntentionQueryRequest, 227 reply *structs.IndexedIntentionMatches) error { 228 // Forward if necessary 229 if done, err := s.srv.forward("Intention.Match", args, args, reply); done { 230 return err 231 } 232 233 // Get the ACL token for the request for the checks below. 234 rule, err := s.srv.ResolveToken(args.Token) 235 if err != nil { 236 return err 237 } 238 239 if rule != nil { 240 // We go through each entry and test the destination to check if it 241 // matches. 242 for _, entry := range args.Match.Entries { 243 if prefix := entry.Name; prefix != "" && !rule.IntentionRead(prefix) { 244 s.srv.logger.Printf("[WARN] consul.intention: Operation on intention prefix '%s' denied due to ACLs", prefix) 245 return acl.ErrPermissionDenied 246 } 247 } 248 } 249 250 return s.srv.blockingQuery( 251 &args.QueryOptions, 252 &reply.QueryMeta, 253 func(ws memdb.WatchSet, state *state.Store) error { 254 index, matches, err := state.IntentionMatch(ws, args.Match) 255 if err != nil { 256 return err 257 } 258 259 reply.Index = index 260 reply.Matches = matches 261 return nil 262 }, 263 ) 264 } 265 266 // Check tests a source/destination and returns whether it would be allowed 267 // or denied based on the current ACL configuration. 268 // 269 // Note: Whenever the logic for this method is changed, you should take 270 // a look at the agent authorize endpoint (agent/agent_endpoint.go) since 271 // the logic there is similar. 272 func (s *Intention) Check( 273 args *structs.IntentionQueryRequest, 274 reply *structs.IntentionQueryCheckResponse) error { 275 // Forward maybe 276 if done, err := s.srv.forward("Intention.Check", args, args, reply); done { 277 return err 278 } 279 280 // Get the test args, and defensively guard against nil 281 query := args.Check 282 if query == nil { 283 return errors.New("Check must be specified on args") 284 } 285 286 // Build the URI 287 var uri connect.CertURI 288 switch query.SourceType { 289 case structs.IntentionSourceConsul: 290 uri = &connect.SpiffeIDService{ 291 Namespace: query.SourceNS, 292 Service: query.SourceName, 293 } 294 295 default: 296 return fmt.Errorf("unsupported SourceType: %q", query.SourceType) 297 } 298 299 // Get the ACL token for the request for the checks below. 300 rule, err := s.srv.ResolveToken(args.Token) 301 if err != nil { 302 return err 303 } 304 305 // Perform the ACL check. For Check we only require ServiceRead and 306 // NOT IntentionRead because the Check API only returns pass/fail and 307 // returns no other information about the intentions used. 308 if prefix, ok := query.GetACLPrefix(); ok { 309 if rule != nil && !rule.ServiceRead(prefix) { 310 s.srv.logger.Printf("[WARN] consul.intention: test on intention '%s' denied due to ACLs", prefix) 311 return acl.ErrPermissionDenied 312 } 313 } 314 315 // Get the matches for this destination 316 state := s.srv.fsm.State() 317 _, matches, err := state.IntentionMatch(nil, &structs.IntentionQueryMatch{ 318 Type: structs.IntentionMatchDestination, 319 Entries: []structs.IntentionMatchEntry{ 320 structs.IntentionMatchEntry{ 321 Namespace: query.DestinationNS, 322 Name: query.DestinationName, 323 }, 324 }, 325 }) 326 if err != nil { 327 return err 328 } 329 if len(matches) != 1 { 330 // This should never happen since the documented behavior of the 331 // Match call is that it'll always return exactly the number of results 332 // as entries passed in. But we guard against misbehavior. 333 return errors.New("internal error loading matches") 334 } 335 336 // Check the authorization for each match 337 for _, ixn := range matches[0] { 338 if auth, ok := uri.Authorize(ixn); ok { 339 reply.Allowed = auth 340 return nil 341 } 342 } 343 344 // No match, we need to determine the default behavior. We do this by 345 // specifying the anonymous token token, which will get that behavior. 346 // The default behavior if ACLs are disabled is to allow connections 347 // to mimic the behavior of Consul itself: everything is allowed if 348 // ACLs are disabled. 349 // 350 // NOTE(mitchellh): This is the same behavior as the agent authorize 351 // endpoint. If this behavior is incorrect, we should also change it there 352 // which is much more important. 353 rule, err = s.srv.ResolveToken("") 354 if err != nil { 355 return err 356 } 357 358 reply.Allowed = true 359 if rule != nil { 360 reply.Allowed = rule.IntentionDefaultAllow() 361 } 362 363 return nil 364 }