2022-02-28 03:15:01 +00:00
package commands
import (
"errors"
"fmt"
"net"
"net/url"
"strings"
"github.com/spf13/cobra"
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/configuration/validator"
)
2022-12-22 00:21:29 +00:00
func newAccessControlCommand ( ctx * CmdCtx ) ( cmd * cobra . Command ) {
2022-02-28 03:15:01 +00:00
cmd = & cobra . Command {
2022-06-14 12:40:00 +00:00
Use : "access-control" ,
Short : cmdAutheliaAccessControlShort ,
Long : cmdAutheliaAccessControlLong ,
Example : cmdAutheliaAccessControlExample ,
2022-09-01 02:24:47 +00:00
DisableAutoGenTag : true ,
2022-02-28 03:15:01 +00:00
}
cmd . AddCommand (
2022-12-22 00:21:29 +00:00
newAccessControlCheckCommand ( ctx ) ,
2022-02-28 03:15:01 +00:00
)
return cmd
}
2022-12-22 00:21:29 +00:00
func newAccessControlCheckCommand ( ctx * CmdCtx ) ( cmd * cobra . Command ) {
2022-02-28 03:15:01 +00:00
cmd = & cobra . Command {
2022-06-14 12:40:00 +00:00
Use : "check-policy" ,
Short : cmdAutheliaAccessControlCheckPolicyShort ,
Long : cmdAutheliaAccessControlCheckPolicyLong ,
Example : cmdAutheliaAccessControlCheckPolicyExample ,
2022-12-22 00:21:29 +00:00
PreRunE : ctx . ChainRunE (
ctx . ConfigLoadRunE ,
) ,
RunE : ctx . AccessControlCheckRunE ,
2022-09-01 02:24:47 +00:00
DisableAutoGenTag : true ,
2022-02-28 03:15:01 +00:00
}
cmd . Flags ( ) . String ( "url" , "" , "the url of the object" )
cmd . Flags ( ) . String ( "method" , "GET" , "the HTTP method of the object" )
cmd . Flags ( ) . String ( "username" , "" , "the username of the subject" )
cmd . Flags ( ) . StringSlice ( "groups" , nil , "the groups of the subject" )
cmd . Flags ( ) . String ( "ip" , "" , "the ip of the subject" )
cmd . Flags ( ) . Bool ( "verbose" , false , "enables verbose output" )
return cmd
}
2022-12-22 00:21:29 +00:00
func ( ctx * CmdCtx ) AccessControlCheckRunE ( cmd * cobra . Command , _ [ ] string ) ( err error ) {
validator . ValidateAccessControl ( ctx . config , ctx . cconfig . validator )
2022-02-28 03:15:01 +00:00
2022-12-22 00:21:29 +00:00
if ctx . cconfig . validator . HasErrors ( ) || ctx . cconfig . validator . HasWarnings ( ) {
2022-02-28 03:15:01 +00:00
return errors . New ( "your configuration has errors" )
}
2022-12-22 00:21:29 +00:00
authorizer := authorization . NewAuthorizer ( ctx . config )
2022-02-28 03:15:01 +00:00
subject , object , err := getSubjectAndObjectFromFlags ( cmd )
if err != nil {
return err
}
results := authorizer . GetRuleMatchResults ( subject , object )
if len ( results ) == 0 {
2022-12-22 00:21:29 +00:00
fmt . Printf ( "\nThe default policy '%s' will be applied to ALL requests as no rules are configured.\n\n" , ctx . config . AccessControl . DefaultPolicy )
2022-02-28 03:15:01 +00:00
return nil
}
verbose , err := cmd . Flags ( ) . GetBool ( "verbose" )
if err != nil {
return err
}
2022-12-22 00:21:29 +00:00
accessControlCheckWriteOutput ( object , subject , results , ctx . config . AccessControl . DefaultPolicy , verbose )
2022-02-28 03:15:01 +00:00
return nil
}
func accessControlCheckWriteObjectSubject ( object authorization . Object , subject authorization . Subject ) {
output := strings . Builder { }
output . WriteString ( fmt . Sprintf ( "Performing policy check for request to '%s'" , object . String ( ) ) )
if object . Method != "" {
output . WriteString ( fmt . Sprintf ( " method '%s'" , object . Method ) )
}
if subject . Username != "" {
output . WriteString ( fmt . Sprintf ( " username '%s'" , subject . Username ) )
}
if len ( subject . Groups ) != 0 {
output . WriteString ( fmt . Sprintf ( " groups '%s'" , strings . Join ( subject . Groups , "," ) ) )
}
if subject . IP != nil {
output . WriteString ( fmt . Sprintf ( " from IP '%s'" , subject . IP . String ( ) ) )
}
output . WriteString ( ".\n" )
fmt . Println ( output . String ( ) )
}
func accessControlCheckWriteOutput ( object authorization . Object , subject authorization . Subject , results [ ] authorization . RuleMatchResult , defaultPolicy string , verbose bool ) {
accessControlCheckWriteObjectSubject ( object , subject )
fmt . Printf ( " #\tDomain\tResource\tMethod\tNetwork\tSubject\n" )
var (
appliedPos int
applied authorization . RuleMatchResult
potentialPos int
potential authorization . RuleMatchResult
)
for i , result := range results {
if result . Skipped && ! verbose {
break
}
switch {
case result . IsMatch ( ) && ! result . Skipped :
appliedPos , applied = i + 1 , result
fmt . Printf ( "* %d\t%s\t%s\t\t%s\t%s\t%s\n" , i + 1 , hitMissMay ( result . MatchDomain ) , hitMissMay ( result . MatchResources ) , hitMissMay ( result . MatchMethods ) , hitMissMay ( result . MatchNetworks ) , hitMissMay ( result . MatchSubjects , result . MatchSubjectsExact ) )
case result . IsPotentialMatch ( ) && ! result . Skipped :
if potentialPos == 0 {
potentialPos , potential = i + 1 , result
}
fmt . Printf ( "~ %d\t%s\t%s\t\t%s\t%s\t%s\n" , i + 1 , hitMissMay ( result . MatchDomain ) , hitMissMay ( result . MatchResources ) , hitMissMay ( result . MatchMethods ) , hitMissMay ( result . MatchNetworks ) , hitMissMay ( result . MatchSubjects , result . MatchSubjectsExact ) )
default :
fmt . Printf ( " %d\t%s\t%s\t\t%s\t%s\t%s\n" , i + 1 , hitMissMay ( result . MatchDomain ) , hitMissMay ( result . MatchResources ) , hitMissMay ( result . MatchMethods ) , hitMissMay ( result . MatchNetworks ) , hitMissMay ( result . MatchSubjects , result . MatchSubjectsExact ) )
}
}
switch {
case appliedPos != 0 && ( potentialPos == 0 || ( potentialPos > appliedPos ) ) :
2022-12-17 12:39:24 +00:00
fmt . Printf ( "\nThe policy '%s' from rule #%d will be applied to this request.\n\n" , applied . Rule . Policy , appliedPos )
2022-02-28 03:15:01 +00:00
case potentialPos != 0 && appliedPos != 0 :
2022-12-17 12:39:24 +00:00
fmt . Printf ( "\nThe policy '%s' from rule #%d will potentially be applied to this request. If not policy '%s' from rule #%d will be.\n\n" , potential . Rule . Policy , potentialPos , applied . Rule . Policy , appliedPos )
2022-02-28 03:15:01 +00:00
case potentialPos != 0 :
2022-12-17 12:39:24 +00:00
fmt . Printf ( "\nThe policy '%s' from rule #%d will potentially be applied to this request. Otherwise the policy '%s' from the default policy will be.\n\n" , potential . Rule . Policy , potentialPos , defaultPolicy )
2022-02-28 03:15:01 +00:00
default :
fmt . Printf ( "\nThe policy '%s' from the default policy will be applied to this request as no rules matched the request.\n\n" , defaultPolicy )
}
}
func hitMissMay ( in ... bool ) ( out string ) {
var hit , miss bool
for _ , x := range in {
if x {
hit = true
} else {
miss = true
}
}
switch {
case hit && miss :
return "may"
case hit :
return "hit"
default :
return "miss"
}
}
func getSubjectAndObjectFromFlags ( cmd * cobra . Command ) ( subject authorization . Subject , object authorization . Object , err error ) {
requestURL , err := cmd . Flags ( ) . GetString ( "url" )
if err != nil {
return subject , object , err
}
2022-09-03 01:51:02 +00:00
parsedURL , err := url . ParseRequestURI ( requestURL )
2022-02-28 03:15:01 +00:00
if err != nil {
return subject , object , err
}
method , err := cmd . Flags ( ) . GetString ( "method" )
if err != nil {
return subject , object , err
}
username , err := cmd . Flags ( ) . GetString ( "username" )
if err != nil {
return subject , object , err
}
groups , err := cmd . Flags ( ) . GetStringSlice ( "groups" )
if err != nil {
return subject , object , err
}
remoteIP , err := cmd . Flags ( ) . GetString ( "ip" )
if err != nil {
return subject , object , err
}
parsedIP := net . ParseIP ( remoteIP )
subject = authorization . Subject {
Username : username ,
Groups : groups ,
IP : parsedIP ,
}
object = authorization . NewObject ( parsedURL , method )
return subject , object , nil
}