mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-28 10:51:44 +01:00 
			
		
		
		
	Added an OIDC AllowGroups option for authorization.
This commit is contained in:
		
							parent
							
								
									4453728614
								
							
						
					
					
						commit
						70f2f5d750
					
				@ -2,6 +2,7 @@
 | 
			
		||||
 | 
			
		||||
## 0.18.x (2022-xx-xx)
 | 
			
		||||
 | 
			
		||||
- Added an OIDC AllowGroups Configuration options and authorization check [#1041](https://github.com/juanfont/headscale/pull/1041)
 | 
			
		||||
- Reworked routing and added support for subnet router failover [#1024](https://github.com/juanfont/headscale/pull/1024)
 | 
			
		||||
 | 
			
		||||
### Changes
 | 
			
		||||
 | 
			
		||||
@ -273,6 +273,9 @@ unix_socket_permission: "0770"
 | 
			
		||||
#
 | 
			
		||||
#   allowed_domains:
 | 
			
		||||
#     - example.com
 | 
			
		||||
# Groups from keycloak have a leading '/'
 | 
			
		||||
#   allowed_groups:
 | 
			
		||||
#     - /headscale
 | 
			
		||||
#   allowed_users:
 | 
			
		||||
#     - alice@example.com
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
@ -96,6 +96,7 @@ type OIDCConfig struct {
 | 
			
		||||
	ExtraParams                map[string]string
 | 
			
		||||
	AllowedDomains             []string
 | 
			
		||||
	AllowedUsers               []string
 | 
			
		||||
	AllowedGroups              []string
 | 
			
		||||
	StripEmaildomain           bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -568,6 +569,7 @@ func GetHeadscaleConfig() (*Config, error) {
 | 
			
		||||
			ExtraParams:      viper.GetStringMapString("oidc.extra_params"),
 | 
			
		||||
			AllowedDomains:   viper.GetStringSlice("oidc.allowed_domains"),
 | 
			
		||||
			AllowedUsers:     viper.GetStringSlice("oidc.allowed_users"),
 | 
			
		||||
			AllowedGroups:    viper.GetStringSlice("oidc.allowed_groups"),
 | 
			
		||||
			StripEmaildomain: viper.GetBool("oidc.strip_email_domain"),
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										38
									
								
								oidc.go
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								oidc.go
									
									
									
									
									
								
							@ -25,6 +25,7 @@ const (
 | 
			
		||||
	errEmptyOIDCCallbackParams = Error("empty OIDC callback params")
 | 
			
		||||
	errNoOIDCIDToken           = Error("could not extract ID Token for OIDC callback")
 | 
			
		||||
	errOIDCAllowedDomains      = Error("authenticated principal does not match any allowed domain")
 | 
			
		||||
	errOIDCAllowedGroups       = Error("authenticated principal is not in any allowed group")
 | 
			
		||||
	errOIDCAllowedUsers        = Error("authenticated principal does not match any allowed user")
 | 
			
		||||
	errOIDCInvalidMachineState = Error("requested machine state key expired before authorisation completed")
 | 
			
		||||
	errOIDCNodeKeyMissing      = Error("could not get node key from cache")
 | 
			
		||||
@ -209,6 +210,10 @@ func (h *Headscale) OIDCCallback(
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := validateOIDCAllowedGroups(writer, h.cfg.OIDC.AllowedGroups, claims); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := validateOIDCAllowedUsers(writer, h.cfg.OIDC.AllowedUsers, claims); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@ -404,6 +409,39 @@ func validateOIDCAllowedDomains(
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// validateOIDCAllowedGroups checks if AllowedGroups is provided,
 | 
			
		||||
// and that the user has one group in the list.
 | 
			
		||||
// claims.Groups can be populated by adding a client scope named
 | 
			
		||||
// 'groups' that contains group membership.
 | 
			
		||||
func validateOIDCAllowedGroups(
 | 
			
		||||
	writer http.ResponseWriter,
 | 
			
		||||
	allowedGroups []string,
 | 
			
		||||
	claims *IDTokenClaims,
 | 
			
		||||
) error {
 | 
			
		||||
	if len(allowedGroups) > 0 {
 | 
			
		||||
		for _, group := range allowedGroups {
 | 
			
		||||
			if IsStringInSlice(claims.Groups, group) {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		log.Error().Msg("authenticated principal not in any allowed groups")
 | 
			
		||||
		writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
 | 
			
		||||
		writer.WriteHeader(http.StatusBadRequest)
 | 
			
		||||
		_, err := writer.Write([]byte("unauthorized principal (allowed groups)"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error().
 | 
			
		||||
				Caller().
 | 
			
		||||
				Err(err).
 | 
			
		||||
				Msg("Failed to write response")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return errOIDCAllowedGroups
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// validateOIDCAllowedUsers checks that if AllowedUsers is provided,
 | 
			
		||||
// that the authenticated principal is part of that list.
 | 
			
		||||
func validateOIDCAllowedUsers(
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user