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  }