1
0
mirror of https://github.com/greenpau/caddy-security.git synced 2025-04-18 08:04:02 +03:00
caddy-security/plugin_authz.go
Paul Greenberg 44a1711ec0 upgrade to github.com/greenpau/go-authcrunc v1.0.20
More info:
* split backends to identity stores and providers
* oauth: use first available key to validate token when kid not found (greenpau/caddy-security#77)
2022-03-30 18:39:01 -04:00

165 lines
4.8 KiB
Go

// Copyright 2022 Paul Greenberg greenpau@outlook.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package security
import (
"fmt"
"net/http"
"strings"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/caddyauth"
"github.com/greenpau/caddy-security/pkg/util"
"github.com/greenpau/go-authcrunch/pkg/authz"
"github.com/greenpau/go-authcrunch/pkg/errors"
"github.com/greenpau/go-authcrunch/pkg/requests"
addrutil "github.com/greenpau/go-authcrunch/pkg/util/addr"
)
const (
authzPluginName = "authorizer"
)
func init() {
caddy.RegisterModule(AuthzMiddleware{})
}
// AuthzMiddleware authorizes access to endpoints based on
// the presense and content of JWT token.
type AuthzMiddleware struct {
RouteMatcher string `json:"route_matcher,omitempty" xml:"route_matcher,omitempty" yaml:"route_matcher,omitempty"`
GatekeeperName string `json:"gatekeeper_name,omitempty" xml:"gatekeeper_name,omitempty" yaml:"gatekeeper_name,omitempty"`
gatekeeper *authz.Gatekeeper
}
// CaddyModule returns the Caddy module information.
func (AuthzMiddleware) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "http.authentication.providers.authorizer",
New: func() caddy.Module { return new(AuthzMiddleware) },
}
}
// Provision provisions Authorizer.
func (m *AuthzMiddleware) Provision(ctx caddy.Context) error {
appModule, err := ctx.App("security")
if err != nil {
return err
}
app := appModule.(*App)
if app == nil {
return fmt.Errorf("security app is nil")
}
if app.Config == nil {
return fmt.Errorf("security app config is nil")
}
gatekeeper, err := app.getGatekeeper(m.GatekeeperName)
if err != nil {
return fmt.Errorf("security app erred with %q authorization policy: %v", m.GatekeeperName, err)
}
m.gatekeeper = gatekeeper
return nil
}
// UnmarshalCaddyfile unmarshals caddyfile.
func (m *AuthzMiddleware) UnmarshalCaddyfile(d *caddyfile.Dispenser) (err error) {
cfg, err := parseAuthzPluginCaddyfile(httpcaddyfile.Helper{Dispenser: d})
if err != nil {
return err
}
m.RouteMatcher = cfg["path"]
m.GatekeeperName = cfg["gatekeeper_name"]
return nil
}
// Validate implements caddy.Validator.
func (m *AuthzMiddleware) Validate() error {
if m.RouteMatcher == "" {
return fmt.Errorf("empty route matcher")
}
if m.GatekeeperName == "" {
return fmt.Errorf("empty gatekeeper name")
}
if m.gatekeeper == nil {
return fmt.Errorf("gatekeeper is nil")
}
return nil
}
// Authenticate authorizes access based on the presense and content of
// authorization token.
func (m AuthzMiddleware) Authenticate(w http.ResponseWriter, r *http.Request) (caddyauth.User, bool, error) {
ar := requests.NewAuthorizationRequest()
ar.ID = util.GetRequestID(r)
if err := m.gatekeeper.Authenticate(w, r, ar); err != nil {
return caddyauth.User{}, false, errors.ErrAuthorizationFailed.WithArgs(
getAuthorizationDetails(r, ar), err,
)
}
if ar.Response.Bypassed {
return caddyauth.User{}, ar.Response.Bypassed, nil
}
if ar.Response.User == nil {
return caddyauth.User{}, false, errors.ErrAuthorizationFailed.WithArgs(
getAuthorizationDetails(r, ar), "user data not found",
)
}
u := caddyauth.User{
Metadata: map[string]string{
"roles": ar.Response.User["roles"].(string),
},
}
if v, exists := ar.Response.User["id"]; exists {
u.ID = v.(string)
}
for _, k := range []string{"claim_id", "sub", "email", "name"} {
if v, exists := ar.Response.User[k]; exists {
u.Metadata[k] = v.(string)
}
}
return u, ar.Response.Authorized, nil
}
func getAuthorizationDetails(r *http.Request, ar *requests.AuthorizationRequest) string {
var details []string
details = append(details, fmt.Sprintf("src_ip=%s", addrutil.GetSourceAddress(r)))
details = append(details, fmt.Sprintf("src_conn_ip=%s", addrutil.GetSourceConnAddress(r)))
if ar.Response.User != nil {
for k, v := range ar.Response.User {
switch k {
case "email", "sub", "name", "jti":
details = append(details, fmt.Sprintf("%s=%s", k, v.(string)))
}
}
}
return strings.Join(details, ", ")
}
// Interface guards
var (
_ caddy.Provisioner = (*AuthzMiddleware)(nil)
_ caddy.Validator = (*AuthzMiddleware)(nil)
_ caddyauth.Authenticator = (*AuthzMiddleware)(nil)
_ caddyfile.Unmarshaler = (*AuthzMiddleware)(nil)
)