2019-06-20 16:36:40 -07:00
package api
import (
"bufio"
2019-08-15 09:34:54 -07:00
"crypto/x509"
2019-06-20 16:36:40 -07:00
"encoding/base64"
2019-08-15 09:34:54 -07:00
"fmt"
"io/ioutil"
2019-06-20 16:36:40 -07:00
"net/http"
"os"
"strconv"
"strings"
"time"
2020-01-24 15:32:38 -06:00
"github.com/chartmuseum/auth"
2019-07-09 22:23:59 -07:00
"github.com/gorilla/mux"
2019-06-20 16:36:40 -07:00
"golang.org/x/crypto/bcrypt"
2021-12-04 03:50:58 +00:00
"zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/api/config"
2019-06-20 16:36:40 -07:00
)
2020-01-24 15:32:38 -06:00
const (
bearerAuthDefaultAccessEntryType = "repository"
)
func AuthHandler ( c * Controller ) mux . MiddlewareFunc {
2021-05-13 21:59:12 +03:00
if isBearerAuthEnabled ( c . Config ) {
2020-01-24 15:32:38 -06:00
return bearerAuthHandler ( c )
}
return basicAuthHandler ( c )
}
2021-12-13 19:23:31 +00:00
func bearerAuthHandler ( ctlr * Controller ) mux . MiddlewareFunc {
2020-01-24 15:32:38 -06:00
authorizer , err := auth . NewAuthorizer ( & auth . AuthorizerOptions {
2021-12-13 19:23:31 +00:00
Realm : ctlr . Config . HTTP . Auth . Bearer . Realm ,
Service : ctlr . Config . HTTP . Auth . Bearer . Service ,
PublicKeyPath : ctlr . Config . HTTP . Auth . Bearer . Cert ,
2020-01-31 16:46:03 -06:00
AccessEntryType : bearerAuthDefaultAccessEntryType ,
EmptyDefaultNamespace : true ,
2020-01-24 15:32:38 -06:00
} )
if err != nil {
2021-12-13 19:23:31 +00:00
ctlr . Log . Panic ( ) . Err ( err ) . Msg ( "error creating bearer authorizer" )
2020-01-24 15:32:38 -06:00
}
return func ( next http . Handler ) http . Handler {
2021-12-13 19:23:31 +00:00
return http . HandlerFunc ( func ( response http . ResponseWriter , request * http . Request ) {
vars := mux . Vars ( request )
2020-01-24 15:32:38 -06:00
name := vars [ "name" ]
2021-12-13 19:23:31 +00:00
header := request . Header . Get ( "Authorization" )
2020-01-24 15:32:38 -06:00
action := auth . PullAction
2021-12-13 19:23:31 +00:00
if m := request . Method ; m != http . MethodGet && m != http . MethodHead {
2020-01-24 15:32:38 -06:00
action = auth . PushAction
}
permissions , err := authorizer . Authorize ( header , action , name )
if err != nil {
2021-12-13 19:23:31 +00:00
ctlr . Log . Error ( ) . Err ( err ) . Msg ( "issue parsing Authorization header" )
response . Header ( ) . Set ( "Content-Type" , "application/json" )
WriteJSON ( response , http . StatusInternalServerError , NewErrorList ( NewError ( UNSUPPORTED ) ) )
2020-01-24 15:32:38 -06:00
return
}
2021-12-13 19:23:31 +00:00
2020-01-24 15:32:38 -06:00
if ! permissions . Allowed {
2021-12-13 19:23:31 +00:00
authFail ( response , permissions . WWWAuthenticateHeader , 0 )
2020-01-24 15:32:38 -06:00
return
}
2021-12-13 19:23:31 +00:00
next . ServeHTTP ( response , request )
2020-01-24 15:32:38 -06:00
} )
}
2019-06-20 16:36:40 -07:00
}
2020-05-11 15:13:24 -07:00
// nolint:gocyclo // we use closure making this a complex subroutine
2021-12-13 19:23:31 +00:00
func basicAuthHandler ( ctlr * Controller ) mux . MiddlewareFunc {
realm := ctlr . Config . HTTP . Realm
2019-08-28 14:05:16 -07:00
if realm == "" {
realm = "Authorization Required"
}
2020-05-11 15:13:24 -07:00
2019-08-28 14:05:16 -07:00
realm = "Basic realm=" + strconv . Quote ( realm )
2019-08-15 09:34:54 -07:00
// no password based authN, if neither LDAP nor HTTP BASIC is enabled
2021-12-13 19:23:31 +00:00
if ctlr . Config . HTTP . Auth == nil ||
( ctlr . Config . HTTP . Auth . HTPasswd . Path == "" && ctlr . Config . HTTP . Auth . LDAP == nil ) {
2019-07-09 22:23:59 -07:00
return func ( next http . Handler ) http . Handler {
2021-12-13 19:23:31 +00:00
return http . HandlerFunc ( func ( response http . ResponseWriter , request * http . Request ) {
if ctlr . Config . HTTP . AllowReadAccess &&
ctlr . Config . HTTP . TLS . CACert != "" &&
request . TLS . VerifiedChains == nil &&
request . Method != http . MethodGet && request . Method != http . MethodHead {
authFail ( response , realm , 5 ) //nolint:gomnd
2019-08-28 14:05:16 -07:00
return
}
2020-07-10 14:32:58 -07:00
2021-12-13 19:23:31 +00:00
if ( request . Method != http . MethodGet && request . Method != http . MethodHead ) && ctlr . Config . HTTP . ReadOnly {
2020-07-10 14:32:58 -07:00
// Reject modification requests in read-only mode
2021-12-13 19:23:31 +00:00
response . WriteHeader ( http . StatusMethodNotAllowed )
2020-07-10 14:32:58 -07:00
return
}
2021-12-13 19:23:31 +00:00
2019-07-09 22:23:59 -07:00
// Process request
2021-12-13 19:23:31 +00:00
next . ServeHTTP ( response , request )
2019-07-09 22:23:59 -07:00
} )
2019-06-20 16:36:40 -07:00
}
}
credMap := make ( map [ string ] string )
2020-05-11 15:13:24 -07:00
2021-12-13 19:23:31 +00:00
delay := ctlr . Config . HTTP . Auth . FailDelay
2020-05-11 15:13:24 -07:00
2019-08-15 09:34:54 -07:00
var ldapClient * LDAPClient
2021-12-13 19:23:31 +00:00
if ctlr . Config . HTTP . Auth != nil {
if ctlr . Config . HTTP . Auth . LDAP != nil {
ldapConfig := ctlr . Config . HTTP . Auth . LDAP
2019-08-15 09:34:54 -07:00
ldapClient = & LDAPClient {
2021-12-13 19:23:31 +00:00
Host : ldapConfig . Address ,
Port : ldapConfig . Port ,
UseSSL : ! ldapConfig . Insecure ,
SkipTLS : ! ldapConfig . StartTLS ,
Base : ldapConfig . BaseDN ,
BindDN : ldapConfig . BindDN ,
BindPassword : ldapConfig . BindPassword ,
UserFilter : fmt . Sprintf ( "(%s=%%s)" , ldapConfig . UserAttribute ) ,
InsecureSkipVerify : ldapConfig . SkipVerify ,
ServerName : ldapConfig . Address ,
Log : ctlr . Log ,
SubtreeSearch : ldapConfig . SubtreeSearch ,
2019-08-15 09:34:54 -07:00
}
2020-05-11 15:13:24 -07:00
2021-12-13 19:23:31 +00:00
if ctlr . Config . HTTP . Auth . LDAP . CACert != "" {
caCert , err := ioutil . ReadFile ( ctlr . Config . HTTP . Auth . LDAP . CACert )
2019-08-15 09:34:54 -07:00
if err != nil {
panic ( err )
}
2020-05-11 15:13:24 -07:00
2019-08-15 09:34:54 -07:00
caCertPool := x509 . NewCertPool ( )
2020-05-11 15:13:24 -07:00
2019-08-15 09:34:54 -07:00
if ! caCertPool . AppendCertsFromPEM ( caCert ) {
panic ( errors . ErrBadCACert )
}
2020-05-11 15:13:24 -07:00
2019-12-11 12:16:37 -08:00
ldapClient . ClientCAs = caCertPool
2019-08-15 09:34:54 -07:00
} else {
// default to system cert pool
caCertPool , err := x509 . SystemCertPool ( )
if err != nil {
panic ( errors . ErrBadCACert )
}
2020-05-11 15:13:24 -07:00
2019-12-11 12:16:37 -08:00
ldapClient . ClientCAs = caCertPool
2019-08-15 09:34:54 -07:00
}
}
2020-05-11 15:13:24 -07:00
2021-12-13 19:23:31 +00:00
if ctlr . Config . HTTP . Auth . HTPasswd . Path != "" {
credsFile , err := os . Open ( ctlr . Config . HTTP . Auth . HTPasswd . Path )
2019-08-15 09:34:54 -07:00
if err != nil {
panic ( err )
}
2021-12-13 19:23:31 +00:00
defer credsFile . Close ( )
2019-06-20 16:36:40 -07:00
2021-12-13 19:23:31 +00:00
scanner := bufio . NewScanner ( credsFile )
2020-06-09 17:19:01 -04:00
for scanner . Scan ( ) {
line := scanner . Text ( )
if strings . Contains ( line , ":" ) {
tokens := strings . Split ( scanner . Text ( ) , ":" )
credMap [ tokens [ 0 ] ] = tokens [ 1 ]
2019-08-15 09:34:54 -07:00
}
}
2019-06-20 16:36:40 -07:00
}
}
2019-07-09 22:23:59 -07:00
return func ( next http . Handler ) http . Handler {
2021-12-13 19:23:31 +00:00
return http . HandlerFunc ( func ( response http . ResponseWriter , request * http . Request ) {
if ( request . Method == http . MethodGet || request . Method == http . MethodHead ) && ctlr . Config . HTTP . AllowReadAccess {
2019-08-28 14:05:16 -07:00
// Process request
2021-12-13 19:23:31 +00:00
next . ServeHTTP ( response , request )
2019-08-28 14:05:16 -07:00
return
}
2021-12-13 19:23:31 +00:00
if ( request . Method != http . MethodGet && request . Method != http . MethodHead ) && ctlr . Config . HTTP . ReadOnly {
2020-07-10 14:32:58 -07:00
// Reject modification requests in read-only mode
2021-12-13 19:23:31 +00:00
response . WriteHeader ( http . StatusMethodNotAllowed )
2020-07-10 14:32:58 -07:00
return
}
2021-12-13 19:23:31 +00:00
basicAuth := request . Header . Get ( "Authorization" )
2019-07-09 22:23:59 -07:00
if basicAuth == "" {
2021-12-13 19:23:31 +00:00
authFail ( response , realm , delay )
2019-07-09 22:23:59 -07:00
return
}
2019-06-20 16:36:40 -07:00
2021-12-13 19:23:31 +00:00
splitStr := strings . SplitN ( basicAuth , " " , 2 ) //nolint:gomnd
if len ( splitStr ) != 2 || strings . ToLower ( splitStr [ 0 ] ) != "basic" {
authFail ( response , realm , delay )
2020-05-11 15:13:24 -07:00
2019-07-09 22:23:59 -07:00
return
}
2019-06-20 16:36:40 -07:00
2021-12-13 19:23:31 +00:00
decodedStr , err := base64 . StdEncoding . DecodeString ( splitStr [ 1 ] )
2019-07-09 22:23:59 -07:00
if err != nil {
2021-12-13 19:23:31 +00:00
authFail ( response , realm , delay )
2019-07-09 22:23:59 -07:00
return
}
2019-06-20 16:36:40 -07:00
2021-12-13 19:23:31 +00:00
pair := strings . SplitN ( string ( decodedStr ) , ":" , 2 ) //nolint:gomnd
2020-05-11 15:13:24 -07:00
// nolint:gomnd
2019-07-09 22:23:59 -07:00
if len ( pair ) != 2 {
2021-12-13 19:23:31 +00:00
authFail ( response , realm , delay )
2019-07-09 22:23:59 -07:00
return
}
2019-06-20 16:36:40 -07:00
2019-07-09 22:23:59 -07:00
username := pair [ 0 ]
passphrase := pair [ 1 ]
2019-06-20 16:36:40 -07:00
2020-01-15 09:37:17 -08:00
// first, HTTPPassword authN (which is local)
passphraseHash , ok := credMap [ username ]
if ok {
if err := bcrypt . CompareHashAndPassword ( [ ] byte ( passphraseHash ) , [ ] byte ( passphrase ) ) ; err == nil {
// Process request
2021-12-13 19:23:31 +00:00
next . ServeHTTP ( response , request )
2020-01-15 09:37:17 -08:00
return
}
}
// next, LDAP if configured (network-based which can lose connectivity)
2021-12-13 19:23:31 +00:00
if ctlr . Config . HTTP . Auth != nil && ctlr . Config . HTTP . Auth . LDAP != nil {
2019-08-15 09:34:54 -07:00
ok , _ , err := ldapClient . Authenticate ( username , passphrase )
if ok && err == nil {
// Process request
2021-12-13 19:23:31 +00:00
next . ServeHTTP ( response , request )
2019-08-15 09:34:54 -07:00
return
}
}
2021-12-13 19:23:31 +00:00
authFail ( response , realm , delay )
2019-07-09 22:23:59 -07:00
} )
2019-06-20 16:36:40 -07:00
}
}
2020-01-24 15:32:38 -06:00
2021-06-08 23:11:18 +03:00
func isAuthnEnabled ( config * config . Config ) bool {
2021-09-01 12:15:00 +03:00
if config . HTTP . Auth != nil &&
( config . HTTP . Auth . HTPasswd . Path != "" || config . HTTP . Auth . LDAP != nil ) {
return true
}
return false
}
2021-06-08 23:11:18 +03:00
func isBearerAuthEnabled ( config * config . Config ) bool {
2021-09-01 12:15:00 +03:00
if config . HTTP . Auth != nil &&
config . HTTP . Auth . Bearer != nil &&
config . HTTP . Auth . Bearer . Cert != "" &&
config . HTTP . Auth . Bearer . Realm != "" &&
config . HTTP . Auth . Bearer . Service != "" {
return true
}
return false
}
2020-01-24 15:32:38 -06:00
func authFail ( w http . ResponseWriter , realm string , delay int ) {
time . Sleep ( time . Duration ( delay ) * time . Second )
w . Header ( ) . Set ( "WWW-Authenticate" , realm )
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2020-01-30 23:57:03 -08:00
WriteJSON ( w , http . StatusUnauthorized , NewErrorList ( NewError ( UNAUTHORIZED ) ) )
2020-01-24 15:32:38 -06:00
}