You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							232 lines
						
					
					
						
							5.2 KiB
						
					
					
				
			
		
		
	
	
							232 lines
						
					
					
						
							5.2 KiB
						
					
					
				| package main
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"strconv"
 | |
| 
 | |
| 	"github.com/Nerzal/gocloak/v7"
 | |
| 	"github.com/prometheus/client_golang/prometheus"
 | |
| 	l "gitlab.com/cyclops-utilities/logging"
 | |
| )
 | |
| 
 | |
| type basicUserData struct {
 | |
| 	UserSub string
 | |
| }
 | |
| 
 | |
| type userInfo struct {
 | |
| 	Username      string `json:"username"`
 | |
| 	Firstname     string `json:"firstname"`
 | |
| 	Lastname      string `json:"lastname"`
 | |
| 	EmailAddress  string `json:"email"`
 | |
| 	EmailVerified bool   `json:"emailverified"`
 | |
| 	ID            string `json:"id"`
 | |
| }
 | |
| 
 | |
| // AuthAPIKey (Swagger func) assures that the token provided when connecting to
 | |
| // the API is the one presented in the config file as the valid token for the
 | |
| // deployment.
 | |
| func AuthAPIKey(token string) (i interface{}, e error) {
 | |
| 
 | |
| 	l.Warning.Printf("[SECURITY] APIKey Authentication: Trying entry with token: %v\n", token)
 | |
| 
 | |
| 	if !cfg.APIKey.Enabled {
 | |
| 
 | |
| 		e = errors.New("the API Key authentication is not active in this deployment")
 | |
| 
 | |
| 		metricSecurity.With(prometheus.Labels{"mode": "APIKey", "state": "DISABLED"}).Inc()
 | |
| 
 | |
| 		return
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	if cfg.APIKey.Token == token {
 | |
| 
 | |
| 		i = basicUserData{
 | |
| 			UserSub: token,
 | |
| 		}
 | |
| 
 | |
| 		metricSecurity.With(prometheus.Labels{"mode": "APIKey", "state": "ACCESS GRANTED"}).Inc()
 | |
| 
 | |
| 	} else {
 | |
| 
 | |
| 		e = errors.New("provided token is not valid")
 | |
| 
 | |
| 		metricSecurity.With(prometheus.Labels{"mode": "APIKey", "state": "INVALID TOKEN"}).Inc()
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	return
 | |
| 
 | |
| }
 | |
| 
 | |
| // AuthKeycloak (Swagger func) is called whenever it is necessary to check if a
 | |
| // token which is presented to access an API is valid.
 | |
| // The functions assumes that in keycloak the roles are going to be in uppercase
 | |
| // and in the form of SCOPE_ROLE.
 | |
| // Example:
 | |
| //		ADMIN_ROLE <-> scope admin
 | |
| func AuthKeycloak(token string, scopes []string) (i interface{}, returnErr error) {
 | |
| 
 | |
| 	l.Debug.Printf("[SECURITY] AuthKeycloak: Performing authentication check - token = %v...%v\n", token, scopes)
 | |
| 
 | |
| 	if !cfg.Keycloak.Enabled {
 | |
| 
 | |
| 		returnErr = errors.New("the Keycloak authentication is not active in this deployment")
 | |
| 
 | |
| 		metricSecurity.With(prometheus.Labels{"mode": "Keycloak", "state": "DISABLED"}).Inc()
 | |
| 
 | |
| 		return
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	keycloakService := getKeycloakService(cfg.Keycloak)
 | |
| 	client := gocloak.NewClient(keycloakService)
 | |
| 
 | |
| 	ctx := context.Background()
 | |
| 
 | |
| 	_, err := client.LoginClient(ctx, cfg.Keycloak.ClientID, cfg.Keycloak.ClientSecret, cfg.Keycloak.Realm)
 | |
| 	// clientToken, err := client.LoginClient(ctx, cfg.Keycloak.ClientID, cfg.Keycloak.ClientSecret, cfg.Keycloak.Realm)
 | |
| 
 | |
| 	if err != nil {
 | |
| 
 | |
| 		l.Warning.Printf("[SECURITY] Problems logging in to keycloak. Error: %v\n", err.Error())
 | |
| 
 | |
| 		returnErr = errors.New("unable to log in to keycloak")
 | |
| 
 | |
| 		metricSecurity.With(prometheus.Labels{"mode": "Keycloak", "state": "FAIL: Keycloak Server unavailable"}).Inc()
 | |
| 
 | |
| 		return
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	u, err := client.GetUserInfo(ctx, token, cfg.Keycloak.Realm)
 | |
| 
 | |
| 	if err != nil {
 | |
| 
 | |
| 		l.Warning.Printf("[SECURITY] Problems getting user Info. Error: %v\n", err.Error())
 | |
| 
 | |
| 		returnErr = errors.New("unable to get userInfo from keycloak")
 | |
| 
 | |
| 		metricSecurity.With(prometheus.Labels{"mode": "Keycloak", "state": "FAIL: Keycloak Server unavailable userInfo"}).Inc()
 | |
| 
 | |
| 		return
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	if u != nil {
 | |
| 
 | |
| 		i = basicUserData{
 | |
| 			UserSub: *u.Sub,
 | |
| 		}
 | |
| 
 | |
| 		metricSecurity.With(prometheus.Labels{"mode": "Keycloak", "state": "ACCESS GRANTED"}).Inc()
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	// 	userRoles := make(map[string]bool)
 | |
| 	//
 | |
| 	// 	roles, err := client.GetRoleMappingByUserID(ctx, clientToken.AccessToken, cfg.Keycloak.Realm, *u.Sub)
 | |
| 	//
 | |
| 	// 	if err != nil {
 | |
| 	//
 | |
| 	// 		l.Warning.Printf("[SECURITY] Problems getting roles by user ID. Error:\n" + err.Error())
 | |
| 	//
 | |
| 	// 		returnErr = errors.New("unable to retrieve user roles from keycloak")
 | |
| 	//
 | |
| 	// 		return
 | |
| 	//
 | |
| 	// 	}
 | |
| 	//
 | |
| 	// 	for _, m := range roles.RealmMappings {
 | |
| 	//
 | |
| 	// 		userRoles[*m.Name] = true
 | |
| 	//
 | |
| 	// 	}
 | |
| 	//
 | |
| 	// 	ug, err := client.GetUserGroups(ctx, clientToken.AccessToken, cfg.Keycloak.Realm, *u.Sub)
 | |
| 	//
 | |
| 	// 	if err != nil {
 | |
| 	//
 | |
| 	// 		l.Warning.Printf("[SECURITY] Problems getting groups by user ID. Error:\n" + err.Error())
 | |
| 	//
 | |
| 	// 		returnErr = errors.New("unable to get user groups from keycloak")
 | |
| 	//
 | |
| 	// 		return
 | |
| 	//
 | |
| 	// 	}
 | |
| 	//
 | |
| 	// 	for _, m := range ug {
 | |
| 	//
 | |
| 	// 		roles, err := client.GetRoleMappingByGroupID(ctx, clientToken.AccessToken, cfg.Keycloak.Realm, *m.ID)
 | |
| 	//
 | |
| 	// 		if err != nil {
 | |
| 	//
 | |
| 	// 			l.Warning.Printf("[SECURITY] Problems getting roles by group ID. Error:\n" + err.Error())
 | |
| 	//
 | |
| 	// 			returnErr = errors.New("unable get groups roles from keycloak")
 | |
| 	//
 | |
| 	// 			return
 | |
| 	//
 | |
| 	// 		}
 | |
| 	//
 | |
| 	// 		for _, n := range roles.RealmMappings {
 | |
| 	//
 | |
| 	// 			userRoles[*n.Name] = true
 | |
| 	//
 | |
| 	// 		}
 | |
| 	//
 | |
| 	// 	}
 | |
| 	//
 | |
| 	// 	control := false
 | |
| 	//
 | |
| 	// 	for _, sc := range scopes {
 | |
| 	//
 | |
| 	// 		if userRoles[strings.ToUpper(sc)+"_ROLE"] {
 | |
| 	//
 | |
| 	// 			control = true
 | |
| 	//
 | |
| 	// 		}
 | |
| 	//
 | |
| 	// 	}
 | |
| 	//
 | |
| 	// 	if !control {
 | |
| 	//
 | |
| 	// 		returnErr = errors2.New(401, "The required role is not present in the user permissions")
 | |
| 	//
 | |
| 	// 		return
 | |
| 	//
 | |
| 	// 	}
 | |
| 
 | |
| 	return
 | |
| 
 | |
| }
 | |
| 
 | |
| // getKeycloaktService returns the keycloak service; note that there has to be exceptional
 | |
| // handling of port 80 and port 443
 | |
| func getKeycloakService(c keycloakConfig) (s string) {
 | |
| 
 | |
| 	if c.UseHTTP {
 | |
| 
 | |
| 		s = "http://" + c.Host
 | |
| 
 | |
| 		if c.Port != 80 {
 | |
| 
 | |
| 			s = s + ":" + strconv.Itoa(c.Port)
 | |
| 		}
 | |
| 
 | |
| 	} else {
 | |
| 
 | |
| 		s = "https://" + c.Host
 | |
| 
 | |
| 		if c.Port != 443 {
 | |
| 
 | |
| 			s = s + ":" + strconv.Itoa(c.Port)
 | |
| 
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	return
 | |
| 
 | |
| }
 | |
| 
 |