github.com/aporeto-inc/trireme-lib@v10.358.0+incompatible/controller/internal/enforcer/apiauth/apiauth.go (about) 1 package apiauth 2 3 import ( 4 "context" 5 "fmt" 6 "net" 7 "net/http" 8 "strings" 9 "sync" 10 "time" 11 12 "go.aporeto.io/enforcerd/trireme-lib/collector" 13 "go.aporeto.io/enforcerd/trireme-lib/controller/internal/enforcer/applicationproxy/serviceregistry" 14 "go.aporeto.io/enforcerd/trireme-lib/controller/pkg/packet" 15 "go.aporeto.io/enforcerd/trireme-lib/controller/pkg/secrets" 16 "go.aporeto.io/enforcerd/trireme-lib/controller/pkg/servicetokens" 17 "go.aporeto.io/enforcerd/trireme-lib/policy" 18 "go.uber.org/zap" 19 ) 20 21 const ( 22 // DefaultValidity is default service token validity. 23 DefaultValidity = 60 * time.Second 24 25 // TriremeOIDCCallbackURI is the callback URI that must be presented by 26 // any OIDC provider. 27 TriremeOIDCCallbackURI = "/aporeto/oidc/callback" 28 ) 29 30 // Processor is an API Authorization processor. 31 type Processor struct { 32 puContext string 33 34 issuer string // the issuer ID .. need to get rid of that part with the new tokens 35 secrets secrets.Secrets 36 sync.RWMutex 37 } 38 39 // New will create a new authorization processor. 40 func New(contextID string, s secrets.Secrets) *Processor { 41 return &Processor{ 42 puContext: contextID, 43 secrets: s, 44 } 45 } 46 47 func (p *Processor) retrieveNetworkContext(originalIP *net.TCPAddr) (*serviceregistry.PortContext, error) { 48 49 return serviceregistry.Instance().RetrieveExposedServiceContext(originalIP.IP, originalIP.Port, "") 50 } 51 52 func (p *Processor) retrieveApplicationContext(address *net.TCPAddr) (*serviceregistry.ServiceContext, *serviceregistry.DependentServiceData, error) { 53 54 return serviceregistry.Instance().RetrieveDependentServiceDataByIDAndNetwork(p.puContext, address.IP, address.Port, "") 55 } 56 57 // UpdateSecrets is called to update the authorizer secrets. 58 func (p *Processor) UpdateSecrets(s secrets.Secrets) { 59 p.Lock() 60 defer p.Unlock() 61 62 p.secrets = s 63 } 64 65 // ApplicationRequest processes an application side request and returns 66 // the token that is associated with this application, together with an 67 // error if the request must be rejected. 68 func (p *Processor) ApplicationRequest(r *Request) (*AppAuthResponse, error) { 69 70 d := &AppAuthResponse{ 71 TLSListener: true, 72 } 73 74 // Derive the service context for this request. This is another PU 75 // or some external service. Context is derived based on the original 76 // destination of the request. 77 sctx, serviceData, err := p.retrieveApplicationContext(r.OriginalDestination) 78 if err != nil { 79 return d, &AuthError{ 80 status: http.StatusBadGateway, 81 message: fmt.Sprintf("Cannot identify application context: %s", err), 82 } 83 } 84 d.PUContext = sctx.PUContext 85 d.ServiceID = serviceData.APICache.ID 86 87 // First we process network type rules (L3 based decision) 88 _, netaction, noNetAccesPolicy := sctx.PUContext.ApplicationACLPolicyFromAddr(r.OriginalDestination.IP, uint16(r.OriginalDestination.Port), uint8(packet.IPProtocolTCP)) 89 d.NetworkPolicyID = netaction.PolicyID 90 d.NetworkServiceID = netaction.ServiceID 91 if noNetAccesPolicy == nil && netaction.Action.Rejected() { 92 return d, &AuthError{ 93 status: http.StatusNetworkAuthenticationRequired, 94 message: "Unauthorized Service - Rejected Outgoing Request by Network Policies", 95 } 96 } 97 98 // For external services we validate policy at the ingress. 99 if serviceData.APICache.External { 100 d.External = true 101 102 // Get the corresponding scopes 103 found, rule := serviceData.APICache.FindRule(r.Method, r.URL.Path) 104 if !found { 105 return d, &AuthError{ 106 status: http.StatusForbidden, 107 message: "Uknown or unauthorized service: policy not found", 108 } 109 } 110 d.HookMethod = rule.HookMethod 111 // If there is an authorization policy attached to the rule, we must validate 112 // against the identity of the PU. 113 if !rule.Public { 114 // Validate the policy based on the scopes of the PU. 115 // TODO: Add user scopes 116 if !serviceData.APICache.MatchClaims(rule.ClaimMatchingRules, append(sctx.PUContext.Identity().GetSlice(), sctx.PUContext.Scopes()...)) { 117 return d, &AuthError{ 118 status: http.StatusForbidden, 119 message: "Unauthorized service: rejected by policy", 120 } 121 } 122 } 123 124 d.Action = policy.Accept | policy.Log 125 if !serviceData.ServiceObject.NoTLSExternalService { 126 d.Action = d.Action | policy.Encrypt 127 } 128 d.TLSListener = !serviceData.ServiceObject.NoTLSExternalService 129 130 return d, nil 131 } 132 133 p.RLock() 134 defer p.RUnlock() 135 136 secret := p.secrets 137 138 token, err := servicetokens.CreateAndSign( 139 p.issuer, 140 sctx.PUContext.Identity().GetSlice(), 141 sctx.PUContext.Scopes(), 142 sctx.PUContext.ManagementID(), 143 DefaultValidity, 144 secret.EncodingKey(), 145 nil, 146 ) 147 if err != nil { 148 return d, &AuthError{ 149 status: http.StatusInternalServerError, 150 message: "Unable to issue service token", 151 err: err, 152 } 153 } 154 155 d.Token = token 156 157 return d, nil 158 } 159 160 // NetworkRequest authorizes a network request and either accepts the request 161 // or potentially issues a redirect. 162 func (p *Processor) NetworkRequest(ctx context.Context, r *Request) (*NetworkAuthResponse, error) { 163 164 // First retrieve the context and policy for this request. Network 165 // requests are indexed based on the original destination and port. 166 pctx, err := p.retrieveNetworkContext(r.OriginalDestination) 167 if err != nil { 168 return nil, &AuthError{ 169 status: http.StatusInternalServerError, 170 message: "Internal server error - cannot identify destination policy", 171 err: err, 172 } 173 } 174 175 // Create a basic response. We will update this response with information 176 // as we continue processing. 177 d := &NetworkAuthResponse{ 178 PUContext: pctx.PUContext, 179 ServiceID: pctx.Service.ID, 180 Action: policy.Reject, 181 SourceType: collector.EndPointTypeExternalIP, 182 TLSListener: pctx.Service.PrivateTLSListener, 183 Namespace: pctx.PUContext.ManagementNamespace(), 184 } 185 186 // We process first OIDC callbacks. These are the redirects after a user 187 // has been authorized. We do not apply any network rule checks in this 188 // case. If the callback is authorized we return the cookie and JWT 189 // for the user. 190 if strings.HasPrefix(r.RequestURI, TriremeOIDCCallbackURI) { 191 callbackResponse, err := pctx.Authorizer.Callback(ctx, r.URL) 192 if err == nil { 193 d.Action = policy.Accept | policy.Encrypt | policy.Log 194 d.Redirect = true 195 d.RedirectURI = callbackResponse.OriginURL 196 d.Cookie = callbackResponse.Cookie 197 d.Data = callbackResponse.Data 198 d.SourceType = collector.EndPointTypeClaims 199 d.NetworkPolicyID = "default" 200 d.NetworkServiceID = "default" 201 } 202 return d, &AuthError{ 203 message: callbackResponse.Message, 204 status: callbackResponse.Status, 205 } 206 } 207 208 // We first process the network access rules based on external networks or 209 // incoming IP addresses. We cannot process yet the Aporeto authorization 210 // rules until after we decode the claims. The aclPolicy holds the matched 211 // rules. If the method returns no error we store it in the noNetAccessPolicy 212 // variable. This indicates that we have found no external network rule that 213 // allows the request and we must validate the PU to PU rules. We will not 214 // know what to do until after we decode all the incoming claims. 215 // We perform this function early so that we don't waste CPU cycles with 216 // processing tokens if the network policy does not allow the connection. 217 aclReportPolicy, aclActualPolicy, noNetAccessPolicy := pctx.PUContext.NetworkACLPolicyFromAddr( 218 r.SourceAddress.IP, 219 uint16(r.OriginalDestination.Port), 220 uint8(packet.IPProtocolTCP), 221 ) 222 d.NetworkPolicyID = aclActualPolicy.PolicyID 223 d.NetworkServiceID = aclActualPolicy.ServiceID 224 225 if aclActualPolicy.Action.Logged() { 226 d.Action = d.Action | policy.Log 227 } 228 229 if aclReportPolicy.ObserveAction.Observed() { 230 d.ObservedPolicyID = aclReportPolicy.PolicyID 231 d.ObservedAction = aclReportPolicy.Action 232 } 233 234 if noNetAccessPolicy == nil && aclActualPolicy.Action.Rejected() { 235 d.DropReason = collector.PolicyDrop 236 d.SourceType = collector.EndPointTypeExternalIP 237 return d, &AuthError{ 238 message: "Access denied by network policy", 239 status: http.StatusNetworkAuthenticationRequired, 240 } 241 } 242 243 // Retrieve the headers with the key and auth parameters. If the parameters do not 244 // exist, we will end up with empty values, but processing can continue. The authorizer 245 // will validate if they are needed or not. 246 token, key := processHeaders(r) 247 248 // Calculate the user attributes. User attributes can be derived either from a 249 // token or from a certificate. The authorizer library will parse them. We don't 250 // care if there are no user credentials. It might be a request from a PU, 251 // or it might be a request to a public interface. Only if the service mandates 252 // user credentials, we get the redirect directive. 253 userCredentials(ctx, pctx, r, d) 254 255 // Calculate the Aporeto PU claims by parsing the token if it exists. If the token 256 // is empty the DecodeAporetoClaims method will return no error. 257 var aporetoClaims []string 258 var pingPayload *policy.PingPayload 259 d.SourcePUID, aporetoClaims, pingPayload, err = pctx.Authorizer.DecodeAporetoClaims(token, key) 260 if err != nil { 261 d.DropReason = collector.PolicyDrop 262 return d, &AuthError{ 263 message: fmt.Sprintf("Invalid Authorization Token: %s", err), 264 status: http.StatusForbidden, 265 } 266 } 267 268 if pingPayload != nil && pingPayload.PingID != "" { 269 d.PingConfig = &PingConfig{ 270 PingID: pingPayload.PingID, 271 IterationID: pingPayload.IterationID, 272 Claims: aporetoClaims, 273 PayloadSize: len(token) + len(key), 274 } 275 } 276 277 // If the other side is a PU we will always put the source type as PU. 278 isPUSource := false 279 if len(aporetoClaims) > 0 { 280 isPUSource = true 281 d.SourceType = collector.EndPointTypePU 282 } 283 284 // We need to verify network policy, before validating the API policy. If a network 285 // policy has given us an accept because of IP address based ACLs we proceed anyway. 286 // This is rather convoluted, but a user might choose to implement network 287 // policies with ACLs only, and we have to cover this case. 288 if noNetAccessPolicy != nil || aclReportPolicy.ObserveAction.ObserveApply() { 289 290 // If we have not found an IP based access policy and the other side 291 // is a PU we can visit the network rules based on tag authorization. 292 if isPUSource { 293 netReportPolicy, netActualPolicy := pctx.PUContext.SearchRcvRules(policy.NewTagStoreFromSlice(aporetoClaims)) 294 295 d.NetworkPolicyID = netActualPolicy.PolicyID 296 d.NetworkServiceID = aclActualPolicy.ServiceID 297 d.ObservedPolicyID = "" 298 d.ObservedAction = policy.ActionType(0) 299 300 if netReportPolicy.ObserveAction.Observed() { 301 d.ObservedPolicyID = netReportPolicy.PolicyID 302 d.ObservedAction = netReportPolicy.Action 303 } 304 305 if netActualPolicy.Action.Rejected() { 306 d.DropReason = collector.PolicyDrop 307 return d, &AuthError{ 308 message: "Access not authorized by network policy", 309 status: http.StatusNetworkAuthenticationRequired, 310 } 311 } 312 } else { 313 // If no network access policy and no PU claims, this request 314 // is dropped. 315 d.DropReason = collector.PolicyDrop 316 return d, &AuthError{ 317 message: "Access denied by network policy: no policy found", 318 status: http.StatusNetworkAuthenticationRequired, 319 } 320 } 321 } else { 322 if aclActualPolicy.Action.Accepted() { 323 aporetoClaims = append(aporetoClaims, aclActualPolicy.Labels...) 324 } 325 } 326 327 // We can now validate the API authorization. This is the final step 328 // before forwarding. 329 allClaims := append(aporetoClaims, d.UserAttributes...) 330 accept, public := pctx.Authorizer.Check(r.Method, r.URL.Path, allClaims) 331 if !accept && !public { 332 // If the authorization check returns reject, we need to validate 333 // if this is a public request, it will be accepted. 334 d.DropReason = collector.APIPolicyDrop 335 336 // We need to process the redirects here. The reject might be forcing 337 // us to issue a redirect. Redirects are valid only if the source 338 // is a user. It doesn't make sense to redirect a PU. 339 // If the source is not a PU, then ping cannot be enabled. 340 if !isPUSource { 341 authError := &AuthError{ 342 message: "No token presented or invalid token: Please authenticate first", 343 status: http.StatusTemporaryRedirect, 344 } 345 if d.Redirect { 346 d.RedirectURI = pctx.Authorizer.RedirectURI(r.URL.String()) 347 return d, authError 348 } else if len(pctx.Service.UserRedirectOnAuthorizationFail) > 0 { 349 d.RedirectURI = pctx.Service.UserRedirectOnAuthorizationFail + "?failure_message=authorization" 350 return d, authError 351 } 352 } 353 354 zap.L().Debug("No match found for the request or authorization Error", 355 zap.String("Request", r.Method+" "+r.RequestURI), 356 zap.Strings("User Attributes", d.UserAttributes), 357 zap.Strings("Aporeto Claims", aporetoClaims), 358 ) 359 360 return d, &AuthError{ 361 message: fmt.Sprintf("Unauthorized Access to %s", r.URL), 362 status: http.StatusUnauthorized, 363 } 364 } 365 366 d.Action = policy.Accept 367 if r.TLS != nil { 368 d.Action = d.Action | policy.Encrypt 369 } 370 371 if aclActualPolicy.Action.Logged() { 372 d.Action = d.Action | policy.Log 373 } 374 375 // We update the request headers with the claims and pass back 376 // the information. 377 pctx.Authorizer.UpdateRequestHeaders(r.Header, d.UserAttributes) 378 d.Header = r.Header 379 380 return d, nil 381 382 } 383 384 // userCredentials will find all the user credentials in the http request. 385 // TODO: In addition to looking at the headers, we need to look at the parameters 386 // in case authorization is provided there. 387 // It will return the userAttributes and a boolean instructing whether a redirect 388 // must be performed. If no user credentials are found, it will allow processing 389 // to proceed. It might be a 390 func userCredentials(ctx context.Context, pctx *serviceregistry.PortContext, r *Request, d *NetworkAuthResponse) { 391 if r.TLS == nil { 392 return 393 } 394 395 userCerts := r.TLS.PeerCertificates 396 397 var userToken string 398 authToken := r.Header.Get("Authorization") 399 if len(authToken) < 7 { 400 if r.Cookie != nil { 401 userToken = r.Cookie.Value 402 } 403 } else { 404 userToken = strings.TrimPrefix(authToken, "Bearer ") 405 } 406 407 userAttributes, redirect, refreshedToken, err := pctx.Authorizer.DecodeUserClaims(ctx, pctx.Service.ID, userToken, userCerts) 408 if err != nil { 409 zap.L().Warn("Partially failed to extract and decode user claims", zap.Error(err)) 410 } 411 412 if len(userAttributes) > 0 { 413 d.SourceType = collector.EndPointTypeClaims 414 } 415 416 if refreshedToken != userToken { 417 d.Cookie = &http.Cookie{ 418 Name: "X-APORETO-AUTH", 419 Value: refreshedToken, 420 HttpOnly: true, 421 Secure: true, 422 Path: "/", 423 } 424 } 425 426 d.UserAttributes = userAttributes 427 d.Redirect = redirect 428 } 429 430 func processHeaders(r *Request) (string, string) { 431 token := r.Header.Get("X-APORETO-AUTH") 432 if token != "" { 433 r.Header.Del("X-APORETO-AUTH") 434 } 435 key := r.Header.Get("X-APORETO-KEY") 436 if key != "" { 437 r.Header.Del("X-APORETO-KEY") 438 } 439 return token, key 440 }