github.com/pelicanplatform/pelican@v1.0.5/web_ui/authorization.go (about) 1 /*************************************************************** 2 * 3 * Copyright (C) 2023, Pelican Project, Morgridge Institute for Research 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); you 6 * may not use this file except in compliance with the License. You may 7 * obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 ***************************************************************/ 18 package web_ui 19 20 import ( 21 "net/http" 22 "strings" 23 "time" 24 25 "github.com/gin-gonic/gin" 26 "github.com/lestrrat-go/jwx/v2/jwa" 27 "github.com/lestrrat-go/jwx/v2/jwt" 28 "github.com/pelicanplatform/pelican/config" 29 "github.com/pelicanplatform/pelican/param" 30 "github.com/pelicanplatform/pelican/utils" 31 "github.com/pkg/errors" 32 "github.com/prometheus/common/route" 33 ) 34 35 // Create a token for accessing Prometheus /metrics endpoint on 36 // the server itself 37 func createPromMetricToken() (string, error) { 38 serverURL := param.Server_ExternalWebUrl.GetString() 39 tokenExpireTime := param.Monitoring_TokenExpiresIn.GetDuration() 40 41 tok, err := jwt.NewBuilder(). 42 Claim("scope", "monitoring.scrape"). 43 Issuer(serverURL). 44 Audience([]string{serverURL}). 45 Subject(serverURL). 46 Expiration(time.Now().Add(tokenExpireTime)). 47 Build() 48 if err != nil { 49 return "", err 50 } 51 52 key, err := config.GetIssuerPrivateJWK() 53 if err != nil { 54 return "", errors.Wrap(err, "failed to load the director's private JWK") 55 } 56 57 signed, err := jwt.Sign(tok, jwt.WithKey(jwa.ES256, key)) 58 if err != nil { 59 return "", err 60 } 61 return string(signed), nil 62 } 63 64 // Handle the authorization of Prometheus /metrics endpoint by checking 65 // if a valid token is present with correct scope 66 func promMetricAuthHandler(ctx *gin.Context) { 67 if strings.HasPrefix(ctx.Request.URL.Path, "/metrics") { 68 authRequired := param.Monitoring_MetricAuthorization.GetBool() 69 if !authRequired { 70 ctx.Next() 71 return 72 } 73 // Auth is granted if the request is from either 74 // 1.director scraper 2.server (self) scraper 3.authenticated web user (via cookie) 75 authOption := utils.AuthOption{ 76 Sources: []utils.TokenSource{utils.Header, utils.Cookie}, 77 Issuers: []utils.TokenIssuer{utils.Director, utils.Issuer}, 78 Scopes: []string{"monitoring.scrape"}} 79 80 valid := utils.CheckAnyAuth(ctx, authOption) 81 if !valid { 82 ctx.AbortWithStatusJSON(403, gin.H{"error": "Authentication required to access this endpoint."}) 83 } 84 // Valid director/self request, pass to the next handler 85 ctx.Next() 86 } 87 // We don't care about other routes for this handler 88 ctx.Next() 89 } 90 91 // Handle the authorization of Prometheus query engine endpoint at `/api/v1.0/prometheus` 92 func promQueryEngineAuthHandler(av1 *route.Router) gin.HandlerFunc { 93 return func(c *gin.Context) { 94 authOption := utils.AuthOption{ 95 // Cookie for web user access and header for external service like Grafana to access 96 Sources: []utils.TokenSource{utils.Cookie, utils.Header}, 97 Issuers: []utils.TokenIssuer{utils.Issuer}, 98 Scopes: []string{"monitoring.query"}} 99 100 exists := utils.CheckAnyAuth(c, authOption) 101 if exists { 102 av1.ServeHTTP(c.Writer, c.Request) 103 } else { 104 c.JSON(http.StatusForbidden, gin.H{"error": "Correct authorization required to access Prometheus query engine APIs"}) 105 } 106 } 107 }