// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package agent
import (
"fmt"
"net/http"
"strings"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/lib"
)
// aclCreateResponse is used to wrap the ACL ID
type aclBootstrapResponse struct {
ID string
structs . ACLToken
}
var aclDisabled = HTTPError { StatusCode : http . StatusUnauthorized , Reason : "ACL support disabled" }
// checkACLDisabled will return a standard response if ACLs are disabled. This
// returns true if they are disabled and we should not continue.
func ( s * HTTPHandlers ) checkACLDisabled ( ) bool {
if s . agent . config . ACLsEnabled {
return false
}
return true
}
// ACLBootstrap is used to perform a one-time ACL bootstrap operation on
// a cluster to get the first management token.
func ( s * HTTPHandlers ) ACLBootstrap ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
args := structs . ACLInitialTokenBootstrapRequest {
Datacenter : s . agent . config . Datacenter ,
}
// Handle optional request body
if req . ContentLength > 0 {
var bootstrapSecretRequest api . BootstrapRequest
if err := lib . DecodeJSON ( req . Body , & bootstrapSecretRequest ) ; err != nil {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : fmt . Sprintf ( "Request decoding failed: %v" , err ) }
}
args . BootstrapSecret = bootstrapSecretRequest . BootstrapSecret
}
var out structs . ACLToken
err := s . agent . RPC ( req . Context ( ) , "ACL.BootstrapTokens" , & args , & out )
if err != nil {
if strings . Contains ( err . Error ( ) , structs . ACLBootstrapNotAllowedErr . Error ( ) ) {
return nil , acl . PermissionDeniedError { Cause : err . Error ( ) }
} else {
return nil , err
}
}
return & aclBootstrapResponse { ID : out . SecretID , ACLToken : out } , nil
}
func ( s * HTTPHandlers ) ACLReplicationStatus ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
// Note that we do not forward to the ACL DC here. This is a query for
// any DC that's doing replication.
args := structs . DCSpecificRequest { }
s . parseSource ( req , & args . Source )
if done := s . parse ( resp , req , & args . Datacenter , & args . QueryOptions ) ; done {
return nil , nil
}
// Make the request.
var out structs . ACLReplicationStatus
if err := s . agent . RPC ( req . Context ( ) , "ACL.ReplicationStatus" , & args , & out ) ; err != nil {
return nil , err
}
return out , nil
}
func ( s * HTTPHandlers ) ACLPolicyList ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
var args structs . ACLPolicyListRequest
if done := s . parse ( resp , req , & args . Datacenter , & args . QueryOptions ) ; done {
return nil , nil
}
if err := s . parseEntMeta ( req , & args . EnterpriseMeta ) ; err != nil {
return nil , err
}
if args . Datacenter == "" {
args . Datacenter = s . agent . config . Datacenter
}
var out structs . ACLPolicyListResponse
defer setMeta ( resp , & out . QueryMeta )
if err := s . agent . RPC ( req . Context ( ) , "ACL.PolicyList" , & args , & out ) ; err != nil {
return nil , err
}
// make sure we return an array and not nil
if out . Policies == nil {
out . Policies = make ( structs . ACLPolicyListStubs , 0 )
}
return out . Policies , nil
}
func ( s * HTTPHandlers ) ACLPolicyCRUD ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
var fn func ( resp http . ResponseWriter , req * http . Request , policyID string ) ( interface { } , error )
switch req . Method {
case "GET" :
fn = s . ACLPolicyReadByID
case "PUT" :
fn = s . ACLPolicyWrite
case "DELETE" :
fn = s . ACLPolicyDelete
default :
return nil , MethodNotAllowedError { req . Method , [ ] string { "GET" , "PUT" , "DELETE" } }
}
policyID := strings . TrimPrefix ( req . URL . Path , "/v1/acl/policy/" )
if policyID == "" && req . Method != "PUT" {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : "Missing policy ID" }
}
return fn ( resp , req , policyID )
}
func ( s * HTTPHandlers ) ACLPolicyRead ( resp http . ResponseWriter , req * http . Request , policyID , policyName string ) ( interface { } , error ) {
args := structs . ACLPolicyGetRequest {
Datacenter : s . agent . config . Datacenter ,
PolicyID : policyID ,
PolicyName : policyName ,
}
if done := s . parse ( resp , req , & args . Datacenter , & args . QueryOptions ) ; done {
return nil , nil
}
if err := s . parseEntMeta ( req , & args . EnterpriseMeta ) ; err != nil {
return nil , err
}
if args . Datacenter == "" {
args . Datacenter = s . agent . config . Datacenter
}
var out structs . ACLPolicyResponse
defer setMeta ( resp , & out . QueryMeta )
if err := s . agent . RPC ( req . Context ( ) , "ACL.PolicyRead" , & args , & out ) ; err != nil {
// should return permission denied error if missing permissions
return nil , err
}
if out . Policy == nil {
// if no error was returned above, the policy does not exist
resp . WriteHeader ( http . StatusNotFound )
msg := acl . ACLResourceNotExistError ( "policy" , args . EnterpriseMeta )
return nil , HTTPError { StatusCode : http . StatusNotFound , Reason : msg . Error ( ) }
}
return out . Policy , nil
}
func ( s * HTTPHandlers ) ACLPolicyReadByName ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
policyName := strings . TrimPrefix ( req . URL . Path , "/v1/acl/policy/name/" )
if policyName == "" {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : "Missing policy Name" }
}
return s . ACLPolicyRead ( resp , req , "" , policyName )
}
func ( s * HTTPHandlers ) ACLPolicyReadByID ( resp http . ResponseWriter , req * http . Request , policyID string ) ( interface { } , error ) {
return s . ACLPolicyRead ( resp , req , policyID , "" )
}
func ( s * HTTPHandlers ) ACLPolicyCreate ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
return s . aclPolicyWriteInternal ( resp , req , "" , true )
}
func ( s * HTTPHandlers ) ACLPolicyWrite ( resp http . ResponseWriter , req * http . Request , policyID string ) ( interface { } , error ) {
return s . aclPolicyWriteInternal ( resp , req , policyID , false )
}
func ( s * HTTPHandlers ) aclPolicyWriteInternal ( _resp http . ResponseWriter , req * http . Request , policyID string , create bool ) ( interface { } , error ) {
args := structs . ACLPolicySetRequest {
Datacenter : s . agent . config . Datacenter ,
}
s . parseToken ( req , & args . Token )
if err := s . parseEntMeta ( req , & args . Policy . EnterpriseMeta ) ; err != nil {
return nil , err
}
if err := s . rewordUnknownEnterpriseFieldError ( lib . DecodeJSON ( req . Body , & args . Policy ) ) ; err != nil {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : fmt . Sprintf ( "Policy decoding failed: %v" , err ) }
}
if create {
if args . Policy . ID != "" {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : "Cannot specify the ID when creating a new policy" }
}
} else {
if args . Policy . ID != "" && args . Policy . ID != policyID {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : "Policy ID in URL and payload do not match" }
} else if args . Policy . ID == "" {
args . Policy . ID = policyID
}
}
var out structs . ACLPolicy
if err := s . agent . RPC ( req . Context ( ) , "ACL.PolicySet" , args , & out ) ; err != nil {
return nil , err
}
return & out , nil
}
func ( s * HTTPHandlers ) ACLPolicyDelete ( resp http . ResponseWriter , req * http . Request , policyID string ) ( interface { } , error ) {
args := structs . ACLPolicyDeleteRequest {
Datacenter : s . agent . config . Datacenter ,
PolicyID : policyID ,
}
s . parseToken ( req , & args . Token )
if err := s . parseEntMeta ( req , & args . EnterpriseMeta ) ; err != nil {
return nil , err
}
var ignored string
if err := s . agent . RPC ( req . Context ( ) , "ACL.PolicyDelete" , args , & ignored ) ; err != nil {
if strings . Contains ( err . Error ( ) , acl . ErrNotFound . Error ( ) ) {
resp . WriteHeader ( http . StatusNotFound )
return nil , HTTPError { StatusCode : http . StatusNotFound , Reason : "Cannot find policy to delete" }
}
return nil , err
}
return true , nil
}
func ( s * HTTPHandlers ) ACLTokenList ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
args := & structs . ACLTokenListRequest {
IncludeLocal : true ,
IncludeGlobal : true ,
}
if done := s . parse ( resp , req , & args . Datacenter , & args . QueryOptions ) ; done {
return nil , nil
}
if err := s . parseEntMeta ( req , & args . EnterpriseMeta ) ; err != nil {
return nil , err
}
if args . Datacenter == "" {
args . Datacenter = s . agent . config . Datacenter
}
args . Policy = req . URL . Query ( ) . Get ( "policy" )
args . Role = req . URL . Query ( ) . Get ( "role" )
args . AuthMethod = req . URL . Query ( ) . Get ( "authmethod" )
if err := parseACLAuthMethodEnterpriseMeta ( req , & args . ACLAuthMethodEnterpriseMeta ) ; err != nil {
return nil , err
}
var out structs . ACLTokenListResponse
defer setMeta ( resp , & out . QueryMeta )
if err := s . agent . RPC ( req . Context ( ) , "ACL.TokenList" , & args , & out ) ; err != nil {
return nil , err
}
return out . Tokens , nil
}
func ( s * HTTPHandlers ) ACLTokenCRUD ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
var fn func ( resp http . ResponseWriter , req * http . Request , tokenAccessorID string ) ( interface { } , error )
switch req . Method {
case "GET" :
fn = s . ACLTokenGet
case "PUT" :
fn = s . ACLTokenSet
case "DELETE" :
fn = s . ACLTokenDelete
default :
return nil , MethodNotAllowedError { req . Method , [ ] string { "GET" , "PUT" , "DELETE" } }
}
tokenAccessorID := strings . TrimPrefix ( req . URL . Path , "/v1/acl/token/" )
if strings . HasSuffix ( tokenAccessorID , "/clone" ) && req . Method == "PUT" {
tokenAccessorID = tokenAccessorID [ : len ( tokenAccessorID ) - 6 ]
fn = s . ACLTokenClone
}
if tokenAccessorID == "" && req . Method != "PUT" {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : "Missing token AccessorID" }
}
return fn ( resp , req , tokenAccessorID )
}
func ( s * HTTPHandlers ) ACLTokenSelf ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
args := structs . ACLTokenGetRequest {
TokenIDType : structs . ACLTokenSecret ,
}
if done := s . parse ( resp , req , & args . Datacenter , & args . QueryOptions ) ; done {
return nil , nil
}
// copy the token secret parameter to the ID
args . TokenID = args . Token
if args . Datacenter == "" {
args . Datacenter = s . agent . config . Datacenter
}
var out structs . ACLTokenResponse
defer setMeta ( resp , & out . QueryMeta )
if err := s . agent . RPC ( req . Context ( ) , "ACL.TokenRead" , & args , & out ) ; err != nil {
// should return permission denied error if missing permissions
return nil , err
}
if out . Token == nil {
// if no error was returned above, the token does not exist
resp . WriteHeader ( http . StatusNotFound )
return nil , HTTPError { StatusCode : http . StatusNotFound , Reason : "Supplied token does not exist" }
}
return out . Token , nil
}
func ( s * HTTPHandlers ) ACLTokenCreate ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
return s . aclTokenSetInternal ( req , "" , true )
}
func ( s * HTTPHandlers ) ACLTokenGet ( resp http . ResponseWriter , req * http . Request , tokenAccessorID string ) ( interface { } , error ) {
args := structs . ACLTokenGetRequest {
Datacenter : s . agent . config . Datacenter ,
TokenID : tokenAccessorID ,
TokenIDType : structs . ACLTokenAccessor ,
}
if done := s . parse ( resp , req , & args . Datacenter , & args . QueryOptions ) ; done {
return nil , nil
}
if err := s . parseEntMeta ( req , & args . EnterpriseMeta ) ; err != nil {
return nil , err
}
if _ , ok := req . URL . Query ( ) [ "expanded" ] ; ok {
args . Expanded = true
}
if args . Datacenter == "" {
args . Datacenter = s . agent . config . Datacenter
}
var out structs . ACLTokenResponse
defer setMeta ( resp , & out . QueryMeta )
if err := s . agent . RPC ( req . Context ( ) , "ACL.TokenRead" , & args , & out ) ; err != nil {
return nil , err
}
if out . Token == nil {
// if no error was returned above, the token does not exist
resp . WriteHeader ( http . StatusNotFound )
msg := acl . ACLResourceNotExistError ( "token" , args . EnterpriseMeta )
return nil , HTTPError { StatusCode : http . StatusNotFound , Reason : msg . Error ( ) }
}
if args . Expanded {
expanded := & structs . ACLTokenExpanded {
ACLToken : out . Token ,
ExpandedTokenInfo : out . ExpandedTokenInfo ,
}
return expanded , nil
}
return out . Token , nil
}
func ( s * HTTPHandlers ) ACLTokenSet ( _ http . ResponseWriter , req * http . Request , tokenAccessorID string ) ( interface { } , error ) {
return s . aclTokenSetInternal ( req , tokenAccessorID , false )
}
func ( s * HTTPHandlers ) aclTokenSetInternal ( req * http . Request , tokenAccessorID string , create bool ) ( interface { } , error ) {
args := structs . ACLTokenSetRequest {
Datacenter : s . agent . config . Datacenter ,
Create : create ,
}
s . parseToken ( req , & args . Token )
if err := s . parseEntMeta ( req , & args . ACLToken . EnterpriseMeta ) ; err != nil {
return nil , err
}
if err := s . rewordUnknownEnterpriseFieldError ( lib . DecodeJSON ( req . Body , & args . ACLToken ) ) ; err != nil {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : fmt . Sprintf ( "Token decoding failed: %v" , err ) }
}
if ! create && args . ACLToken . AccessorID != tokenAccessorID {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : "Token Accessor ID in URL and payload do not match" }
}
var out structs . ACLToken
if err := s . agent . RPC ( req . Context ( ) , "ACL.TokenSet" , args , & out ) ; err != nil {
return nil , err
}
return & out , nil
}
func ( s * HTTPHandlers ) ACLTokenDelete ( resp http . ResponseWriter , req * http . Request , tokenAccessorID string ) ( interface { } , error ) {
args := structs . ACLTokenDeleteRequest {
Datacenter : s . agent . config . Datacenter ,
TokenID : tokenAccessorID ,
}
s . parseToken ( req , & args . Token )
if err := s . parseEntMeta ( req , & args . EnterpriseMeta ) ; err != nil {
return nil , err
}
var ignored string
if err := s . agent . RPC ( req . Context ( ) , "ACL.TokenDelete" , args , & ignored ) ; err != nil {
if strings . Contains ( err . Error ( ) , acl . ErrNotFound . Error ( ) ) {
resp . WriteHeader ( http . StatusNotFound )
return nil , HTTPError { StatusCode : http . StatusNotFound , Reason : "Cannot find token to delete" }
}
return nil , err
}
return true , nil
}
func ( s * HTTPHandlers ) ACLTokenClone ( resp http . ResponseWriter , req * http . Request , tokenAccessorID string ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
args := structs . ACLTokenSetRequest {
Datacenter : s . agent . config . Datacenter ,
Create : true ,
}
if err := s . parseEntMeta ( req , & args . ACLToken . EnterpriseMeta ) ; err != nil {
return nil , err
}
if err := s . rewordUnknownEnterpriseFieldError ( lib . DecodeJSON ( req . Body , & args . ACLToken ) ) ; err != nil {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : fmt . Sprintf ( "Token decoding failed: %v" , err ) }
}
s . parseToken ( req , & args . Token )
// Set this for the ID to clone
args . ACLToken . AccessorID = tokenAccessorID
var out structs . ACLToken
if err := s . agent . RPC ( req . Context ( ) , "ACL.TokenClone" , args , & out ) ; err != nil {
return nil , err
}
return & out , nil
}
func ( s * HTTPHandlers ) ACLRoleList ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
var args structs . ACLRoleListRequest
if done := s . parse ( resp , req , & args . Datacenter , & args . QueryOptions ) ; done {
return nil , nil
}
if err := s . parseEntMeta ( req , & args . EnterpriseMeta ) ; err != nil {
return nil , err
}
if args . Datacenter == "" {
args . Datacenter = s . agent . config . Datacenter
}
args . Policy = req . URL . Query ( ) . Get ( "policy" )
var out structs . ACLRoleListResponse
defer setMeta ( resp , & out . QueryMeta )
if err := s . agent . RPC ( req . Context ( ) , "ACL.RoleList" , & args , & out ) ; err != nil {
return nil , err
}
// make sure we return an array and not nil
if out . Roles == nil {
out . Roles = make ( structs . ACLRoles , 0 )
}
return out . Roles , nil
}
func ( s * HTTPHandlers ) ACLRoleCRUD ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
var fn func ( resp http . ResponseWriter , req * http . Request , roleID string ) ( interface { } , error )
switch req . Method {
case "GET" :
fn = s . ACLRoleReadByID
case "PUT" :
fn = s . ACLRoleWrite
case "DELETE" :
fn = s . ACLRoleDelete
default :
return nil , MethodNotAllowedError { req . Method , [ ] string { "GET" , "PUT" , "DELETE" } }
}
roleID := strings . TrimPrefix ( req . URL . Path , "/v1/acl/role/" )
if roleID == "" && req . Method != "PUT" {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : "Missing role ID" }
}
return fn ( resp , req , roleID )
}
func ( s * HTTPHandlers ) ACLRoleReadByName ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
roleName := strings . TrimPrefix ( req . URL . Path , "/v1/acl/role/name/" )
if roleName == "" {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : "Missing role Name" }
}
return s . ACLRoleRead ( resp , req , "" , roleName )
}
func ( s * HTTPHandlers ) ACLRoleReadByID ( resp http . ResponseWriter , req * http . Request , roleID string ) ( interface { } , error ) {
return s . ACLRoleRead ( resp , req , roleID , "" )
}
func ( s * HTTPHandlers ) ACLRoleRead ( resp http . ResponseWriter , req * http . Request , roleID , roleName string ) ( interface { } , error ) {
args := structs . ACLRoleGetRequest {
Datacenter : s . agent . config . Datacenter ,
RoleID : roleID ,
RoleName : roleName ,
}
if done := s . parse ( resp , req , & args . Datacenter , & args . QueryOptions ) ; done {
return nil , nil
}
if err := s . parseEntMeta ( req , & args . EnterpriseMeta ) ; err != nil {
return nil , err
}
if args . Datacenter == "" {
args . Datacenter = s . agent . config . Datacenter
}
var out structs . ACLRoleResponse
defer setMeta ( resp , & out . QueryMeta )
if err := s . agent . RPC ( req . Context ( ) , "ACL.RoleRead" , & args , & out ) ; err != nil {
// should return permission denied error if missing permissions
return nil , err
}
if out . Role == nil {
// if not permission denied error is returned above, role does not exist
resp . WriteHeader ( http . StatusNotFound )
msg := acl . ACLResourceNotExistError ( "role" , args . EnterpriseMeta )
return nil , HTTPError { StatusCode : http . StatusNotFound , Reason : msg . Error ( ) }
}
return out . Role , nil
}
func ( s * HTTPHandlers ) ACLRoleCreate ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
return s . ACLRoleWrite ( resp , req , "" )
}
func ( s * HTTPHandlers ) ACLRoleWrite ( resp http . ResponseWriter , req * http . Request , roleID string ) ( interface { } , error ) {
args := structs . ACLRoleSetRequest {
Datacenter : s . agent . config . Datacenter ,
}
s . parseToken ( req , & args . Token )
if err := s . parseEntMeta ( req , & args . Role . EnterpriseMeta ) ; err != nil {
return nil , err
}
if err := s . rewordUnknownEnterpriseFieldError ( lib . DecodeJSON ( req . Body , & args . Role ) ) ; err != nil {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : fmt . Sprintf ( "Role decoding failed: %v" , err ) }
}
if args . Role . ID != "" && args . Role . ID != roleID {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : "Role ID in URL and payload do not match" }
} else if args . Role . ID == "" {
args . Role . ID = roleID
}
var out structs . ACLRole
if err := s . agent . RPC ( req . Context ( ) , "ACL.RoleSet" , args , & out ) ; err != nil {
return nil , err
}
return & out , nil
}
func ( s * HTTPHandlers ) ACLRoleDelete ( resp http . ResponseWriter , req * http . Request , roleID string ) ( interface { } , error ) {
args := structs . ACLRoleDeleteRequest {
Datacenter : s . agent . config . Datacenter ,
RoleID : roleID ,
}
s . parseToken ( req , & args . Token )
if err := s . parseEntMeta ( req , & args . EnterpriseMeta ) ; err != nil {
return nil , err
}
var ignored string
if err := s . agent . RPC ( req . Context ( ) , "ACL.RoleDelete" , args , & ignored ) ; err != nil {
if strings . Contains ( err . Error ( ) , acl . ErrNotFound . Error ( ) ) {
resp . WriteHeader ( http . StatusNotFound )
return nil , HTTPError { StatusCode : http . StatusNotFound , Reason : "Cannot find role to delete" }
}
return nil , err
}
return true , nil
}
func ( s * HTTPHandlers ) ACLBindingRuleList ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
var args structs . ACLBindingRuleListRequest
if done := s . parse ( resp , req , & args . Datacenter , & args . QueryOptions ) ; done {
return nil , nil
}
if err := s . parseEntMeta ( req , & args . EnterpriseMeta ) ; err != nil {
return nil , err
}
if args . Datacenter == "" {
args . Datacenter = s . agent . config . Datacenter
}
args . AuthMethod = req . URL . Query ( ) . Get ( "authmethod" )
var out structs . ACLBindingRuleListResponse
defer setMeta ( resp , & out . QueryMeta )
if err := s . agent . RPC ( req . Context ( ) , "ACL.BindingRuleList" , & args , & out ) ; err != nil {
return nil , err
}
// make sure we return an array and not nil
if out . BindingRules == nil {
out . BindingRules = make ( structs . ACLBindingRules , 0 )
}
return out . BindingRules , nil
}
func ( s * HTTPHandlers ) ACLBindingRuleCRUD ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
var fn func ( resp http . ResponseWriter , req * http . Request , bindingRuleID string ) ( interface { } , error )
switch req . Method {
case "GET" :
fn = s . ACLBindingRuleRead
case "PUT" :
fn = s . ACLBindingRuleWrite
case "DELETE" :
fn = s . ACLBindingRuleDelete
default :
return nil , MethodNotAllowedError { req . Method , [ ] string { "GET" , "PUT" , "DELETE" } }
}
bindingRuleID := strings . TrimPrefix ( req . URL . Path , "/v1/acl/binding-rule/" )
if bindingRuleID == "" && req . Method != "PUT" {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : "Missing binding rule ID" }
}
return fn ( resp , req , bindingRuleID )
}
func ( s * HTTPHandlers ) ACLBindingRuleRead ( resp http . ResponseWriter , req * http . Request , bindingRuleID string ) ( interface { } , error ) {
args := structs . ACLBindingRuleGetRequest {
Datacenter : s . agent . config . Datacenter ,
BindingRuleID : bindingRuleID ,
}
if done := s . parse ( resp , req , & args . Datacenter , & args . QueryOptions ) ; done {
return nil , nil
}
if err := s . parseEntMeta ( req , & args . EnterpriseMeta ) ; err != nil {
return nil , err
}
if args . Datacenter == "" {
args . Datacenter = s . agent . config . Datacenter
}
var out structs . ACLBindingRuleResponse
defer setMeta ( resp , & out . QueryMeta )
if err := s . agent . RPC ( req . Context ( ) , "ACL.BindingRuleRead" , & args , & out ) ; err != nil {
// should return permission denied error if missing permissions
return nil , err
}
if out . BindingRule == nil {
// if no error was returned above, the binding rule does not exist
resp . WriteHeader ( http . StatusNotFound )
msg := acl . ACLResourceNotExistError ( "binding rule" , args . EnterpriseMeta )
return nil , HTTPError { StatusCode : http . StatusNotFound , Reason : msg . Error ( ) }
}
return out . BindingRule , nil
}
func ( s * HTTPHandlers ) ACLBindingRuleCreate ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
return s . ACLBindingRuleWrite ( resp , req , "" )
}
func ( s * HTTPHandlers ) ACLBindingRuleWrite ( resp http . ResponseWriter , req * http . Request , bindingRuleID string ) ( interface { } , error ) {
args := structs . ACLBindingRuleSetRequest { }
s . parseDC ( req , & args . Datacenter )
s . parseToken ( req , & args . Token )
if err := s . parseEntMeta ( req , & args . BindingRule . EnterpriseMeta ) ; err != nil {
return nil , err
}
if err := s . rewordUnknownEnterpriseFieldError ( lib . DecodeJSON ( req . Body , & args . BindingRule ) ) ; err != nil {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : fmt . Sprintf ( "BindingRule decoding failed: %v" , err ) }
}
if args . BindingRule . ID != "" && args . BindingRule . ID != bindingRuleID {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : "BindingRule ID in URL and payload do not match" }
} else if args . BindingRule . ID == "" {
args . BindingRule . ID = bindingRuleID
}
var out structs . ACLBindingRule
if err := s . agent . RPC ( req . Context ( ) , "ACL.BindingRuleSet" , args , & out ) ; err != nil {
return nil , err
}
return & out , nil
}
func ( s * HTTPHandlers ) ACLBindingRuleDelete ( resp http . ResponseWriter , req * http . Request , bindingRuleID string ) ( interface { } , error ) {
args := structs . ACLBindingRuleDeleteRequest {
BindingRuleID : bindingRuleID ,
}
s . parseDC ( req , & args . Datacenter )
s . parseToken ( req , & args . Token )
if err := s . parseEntMeta ( req , & args . EnterpriseMeta ) ; err != nil {
return nil , err
}
var ignored bool
if err := s . agent . RPC ( req . Context ( ) , "ACL.BindingRuleDelete" , args , & ignored ) ; err != nil {
if strings . Contains ( err . Error ( ) , acl . ErrNotFound . Error ( ) ) {
resp . WriteHeader ( http . StatusNotFound )
return nil , HTTPError { StatusCode : http . StatusNotFound , Reason : "Cannot find binding rule to delete" }
}
return nil , err
}
return true , nil
}
func ( s * HTTPHandlers ) ACLAuthMethodList ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
var args structs . ACLAuthMethodListRequest
if done := s . parse ( resp , req , & args . Datacenter , & args . QueryOptions ) ; done {
return nil , nil
}
if err := s . parseEntMeta ( req , & args . EnterpriseMeta ) ; err != nil {
return nil , err
}
if args . Datacenter == "" {
args . Datacenter = s . agent . config . Datacenter
}
var out structs . ACLAuthMethodListResponse
defer setMeta ( resp , & out . QueryMeta )
if err := s . agent . RPC ( req . Context ( ) , "ACL.AuthMethodList" , & args , & out ) ; err != nil {
return nil , err
}
// make sure we return an array and not nil
if out . AuthMethods == nil {
out . AuthMethods = make ( structs . ACLAuthMethodListStubs , 0 )
}
return out . AuthMethods , nil
}
func ( s * HTTPHandlers ) ACLAuthMethodCRUD ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
var fn func ( resp http . ResponseWriter , req * http . Request , methodName string ) ( interface { } , error )
switch req . Method {
case "GET" :
fn = s . ACLAuthMethodRead
case "PUT" :
fn = s . ACLAuthMethodWrite
case "DELETE" :
fn = s . ACLAuthMethodDelete
default :
return nil , MethodNotAllowedError { req . Method , [ ] string { "GET" , "PUT" , "DELETE" } }
}
methodName := strings . TrimPrefix ( req . URL . Path , "/v1/acl/auth-method/" )
if methodName == "" && req . Method != "PUT" {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : "Missing auth method name" }
}
return fn ( resp , req , methodName )
}
func ( s * HTTPHandlers ) ACLAuthMethodRead ( resp http . ResponseWriter , req * http . Request , methodName string ) ( interface { } , error ) {
args := structs . ACLAuthMethodGetRequest {
Datacenter : s . agent . config . Datacenter ,
AuthMethodName : methodName ,
}
if done := s . parse ( resp , req , & args . Datacenter , & args . QueryOptions ) ; done {
return nil , nil
}
if err := s . parseEntMeta ( req , & args . EnterpriseMeta ) ; err != nil {
return nil , err
}
if args . Datacenter == "" {
args . Datacenter = s . agent . config . Datacenter
}
var out structs . ACLAuthMethodResponse
defer setMeta ( resp , & out . QueryMeta )
if err := s . agent . RPC ( req . Context ( ) , "ACL.AuthMethodRead" , & args , & out ) ; err != nil {
// should return permission denied if missing permissions
return nil , err
}
if out . AuthMethod == nil {
// if no error was returned above, the auth method does not exist
resp . WriteHeader ( http . StatusNotFound )
msg := acl . ACLResourceNotExistError ( "auth method" , args . EnterpriseMeta )
return nil , HTTPError { StatusCode : http . StatusNotFound , Reason : msg . Error ( ) }
}
fixupAuthMethodConfig ( out . AuthMethod )
return out . AuthMethod , nil
}
func ( s * HTTPHandlers ) ACLAuthMethodCreate ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
return s . ACLAuthMethodWrite ( resp , req , "" )
}
func ( s * HTTPHandlers ) ACLAuthMethodWrite ( resp http . ResponseWriter , req * http . Request , methodName string ) ( interface { } , error ) {
args := structs . ACLAuthMethodSetRequest { }
s . parseDC ( req , & args . Datacenter )
s . parseToken ( req , & args . Token )
if err := s . parseEntMeta ( req , & args . AuthMethod . EnterpriseMeta ) ; err != nil {
return nil , err
}
if err := s . rewordUnknownEnterpriseFieldError ( lib . DecodeJSON ( req . Body , & args . AuthMethod ) ) ; err != nil {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : fmt . Sprintf ( "AuthMethod decoding failed: %v" , err ) }
}
if methodName != "" {
if args . AuthMethod . Name != "" && args . AuthMethod . Name != methodName {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : "AuthMethod Name in URL and payload do not match" }
} else if args . AuthMethod . Name == "" {
args . AuthMethod . Name = methodName
}
}
var out structs . ACLAuthMethod
if err := s . agent . RPC ( req . Context ( ) , "ACL.AuthMethodSet" , args , & out ) ; err != nil {
return nil , err
}
fixupAuthMethodConfig ( & out )
return & out , nil
}
func ( s * HTTPHandlers ) ACLAuthMethodDelete ( resp http . ResponseWriter , req * http . Request , methodName string ) ( interface { } , error ) {
args := structs . ACLAuthMethodDeleteRequest {
AuthMethodName : methodName ,
}
s . parseDC ( req , & args . Datacenter )
s . parseToken ( req , & args . Token )
if err := s . parseEntMeta ( req , & args . EnterpriseMeta ) ; err != nil {
return nil , err
}
var ignored bool
if err := s . agent . RPC ( req . Context ( ) , "ACL.AuthMethodDelete" , args , & ignored ) ; err != nil {
if strings . Contains ( err . Error ( ) , acl . ErrNotFound . Error ( ) ) {
resp . WriteHeader ( http . StatusNotFound )
return nil , HTTPError { StatusCode : http . StatusNotFound , Reason : "Cannot find auth method to delete" }
}
return nil , err
}
return true , nil
}
func ( s * HTTPHandlers ) ACLLogin ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
args := & structs . ACLLoginRequest {
Datacenter : s . agent . config . Datacenter ,
Auth : & structs . ACLLoginParams { } ,
}
s . parseDC ( req , & args . Datacenter )
if err := s . parseEntMeta ( req , & args . Auth . EnterpriseMeta ) ; err != nil {
return nil , err
}
if err := s . rewordUnknownEnterpriseFieldError ( lib . DecodeJSON ( req . Body , & args . Auth ) ) ; err != nil {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : fmt . Sprintf ( "Failed to decode request body: %v" , err ) }
}
var out structs . ACLToken
if err := s . agent . RPC ( req . Context ( ) , "ACL.Login" , args , & out ) ; err != nil {
return nil , err
}
return & out , nil
}
func ( s * HTTPHandlers ) ACLLogout ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
args := structs . ACLLogoutRequest {
Datacenter : s . agent . config . Datacenter ,
}
s . parseDC ( req , & args . Datacenter )
s . parseToken ( req , & args . Token )
if args . Token == "" {
return nil , HTTPError { StatusCode : http . StatusUnauthorized , Reason : "Supplied token does not exist" }
}
var ignored bool
if err := s . agent . RPC ( req . Context ( ) , "ACL.Logout" , & args , & ignored ) ; err != nil {
return nil , err
}
return true , nil
}
// A hack to fix up the config types inside of the map[string]interface{}
// so that they get formatted correctly during json.Marshal. Without this,
// string values that get converted to []uint8 end up getting output back
// to the user in base64-encoded form.
func fixupAuthMethodConfig ( method * structs . ACLAuthMethod ) {
for k , v := range method . Config {
if raw , ok := v . ( [ ] uint8 ) ; ok {
strVal := structs . Uint8ToString ( raw )
method . Config [ k ] = strVal
}
}
}
func ( s * HTTPHandlers ) ACLAuthorize ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
// At first glance it may appear like this endpoint is going to leak security relevant information.
// There are a number of reason why this is okay.
//
// 1. The authorizations performed here are the same as what would be done if other HTTP APIs
// were used. This is just a way to see if it would be allowed. These authorization checks
// will be logged along with those from the real endpoints. In that respect, you can figure
// out if you have access just as easily by attempting to perform the requested operation.
// 2. In order to use this API you must have a valid ACL token secret.
// 3. Along with #2 you can use the ACL.GetPolicy RPC endpoint which will return a rolled up
// set of policy rules showing your tokens effective policy. This RPC endpoint exposes
// more information than this one and has been around since before v1.0.0. With that other
// endpoint you get to see all things possible rather than having to have a list of things
// you may want to do and to request authorizations for each one.
// 4. In addition to the legacy ACL.GetPolicy RPC endpoint we have an ACL.PolicyResolve and
// ACL.RoleResolve endpoints. These RPC endpoints allow reading roles and policies so long
// as the token used for the request is linked with them. This is needed to allow client
// agents to pull the policy and roles for a token that they are resolving. The only
// alternative to this style of access would be to make every agent use a token
// with acl:read privileges for all policy and role resolution requests. Once you have
// all the associated policies and roles it would be easy enough to recreate the effective
// policy.
const maxRequests = 64
if s . checkACLDisabled ( ) {
return nil , aclDisabled
}
request := structs . RemoteACLAuthorizationRequest {
Datacenter : s . agent . config . Datacenter ,
QueryOptions : structs . QueryOptions {
AllowStale : true ,
RequireConsistent : false ,
} ,
}
var responses [ ] structs . ACLAuthorizationResponse
s . parseToken ( req , & request . Token )
s . parseDC ( req , & request . Datacenter )
if err := decodeBody ( req . Body , & request . Requests ) ; err != nil {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : fmt . Sprintf ( "Failed to decode request body: %v" , err ) }
}
if len ( request . Requests ) > maxRequests {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : fmt . Sprintf ( "Refusing to process more than %d authorizations at once" , maxRequests ) }
}
if len ( request . Requests ) == 0 {
return make ( [ ] structs . ACLAuthorizationResponse , 0 ) , nil
}
if request . Datacenter != "" && request . Datacenter != s . agent . config . Datacenter {
// when we are targeting a datacenter other than our own then we must issue an RPC
// to perform the resolution as it may involve a local token
if err := s . agent . RPC ( req . Context ( ) , "ACL.Authorize" , & request , & responses ) ; err != nil {
return nil , err
}
} else {
authz , err := s . agent . delegate . ResolveTokenAndDefaultMeta ( request . Token , nil , nil )
if err != nil {
return nil , err
}
responses , err = structs . CreateACLAuthorizationResponses ( authz , request . Requests )
if err != nil {
return nil , HTTPError { StatusCode : http . StatusBadRequest , Reason : err . Error ( ) }
}
}
if responses == nil {
responses = make ( [ ] structs . ACLAuthorizationResponse , 0 )
}
return responses , nil
}