Skip to content

Commit

Permalink
Add custom authentication and authorization implementations to avoid …
Browse files Browse the repository at this point in the history
…spawning new processes (#254)

Purpose
To avoid spawning new processes for requests received by docker auth endpoint.
To allow developers to add their own plugins to docker auth.

Approach
Add new custom authentication and authorization implementations by implementing the existing authentication and authorization interfaces. Therefore the developers can add their own plugins with their program logics.

Test environment
go version: go1.12.5 darwin/amd64
OS: Mac OS 10.14.5
  • Loading branch information
tharindulak authored and rojer committed Aug 24, 2019
1 parent b89dec9 commit bfb1517
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 13 deletions.
81 changes: 81 additions & 0 deletions auth_server/authn/plugin_authn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
Copyright 2019 Cesanta Software Ltd.
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
https://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 authn

import (
"fmt"
"plugin"

"github.com/cesanta/glog"
)

type PluginAuthnConfig struct {
PluginPath string `yaml:"plugin_path"`
}

func lookupSymbol(cfg *PluginAuthnConfig) (Authenticator, error) {
// load module
plug, err := plugin.Open(cfg.PluginPath)
if err != nil {
return nil, fmt.Errorf("error while loading authn plugin: %v", err)
}

// look up for Authn
symAuthen, err := plug.Lookup("Authn")
if err != nil {
return nil, fmt.Errorf("error while loading authn exporting the variable: %v", err)
}

// assert that loaded symbol is of a desired type
var authn Authenticator
authn, ok := symAuthen.(Authenticator)
if !ok {
return nil, fmt.Errorf("unexpected type from module symbol. Unable to cast Authn module")
}
return authn, nil
}

func (c *PluginAuthnConfig) Validate() error {
_, err := lookupSymbol(c)
return err
}

type PluginAuthn struct {
cfg *PluginAuthnConfig
Authn Authenticator
}

func (c *PluginAuthn) Authenticate(user string, password PasswordString) (bool, Labels, error) {
// use the plugin
return c.Authn.Authenticate(user, password)
}

func (c *PluginAuthn) Stop() {
}

func (c *PluginAuthn) Name() string {
return "plugin auth"
}

func NewPluginAuthn(cfg *PluginAuthnConfig) (*PluginAuthn, error) {
glog.Infof("Plugin authenticator: %s", cfg)
authn, err := lookupSymbol(cfg)
if err != nil {
return nil, err
}
return &PluginAuthn{Authn: authn}, nil
}
81 changes: 81 additions & 0 deletions auth_server/authz/plugin_authz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
Copyright 2019 Cesanta Software Ltd.
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
https://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 authz

import (
"fmt"
"plugin"

"github.com/cesanta/glog"
)

type PluginAuthzConfig struct {
PluginPath string `yaml:"plugin_path"`
}

func lookupSymbol(cfg *PluginAuthzConfig) (Authorizer, error) {
// load module
plug, err := plugin.Open(cfg.PluginPath)
if err != nil {
return nil, fmt.Errorf("error while loading authz plugin: %v", err)
}

// look up for Authz
symAuthen, err := plug.Lookup("Authz")
if err != nil {
return nil, fmt.Errorf("error while loading authz exporting the variable: %v", err)
}

// assert that loaded symbol is of a desired type
var authz Authorizer
authz, ok := symAuthen.(Authorizer)
if !ok {
return nil, fmt.Errorf("unexpected type from module symbol. Unable to cast Authz module")
}
return authz, nil
}

func (c *PluginAuthzConfig) Validate() error {
_, err := lookupSymbol(c)
return err
}

type PluginAuthz struct {
cfg *PluginAuthzConfig
Authz Authorizer
}

func (c *PluginAuthz) Stop() {
}

func (c *PluginAuthz) Name() string {
return "plugin authz"
}

func NewPluginAuthzAuthorizer(cfg *PluginAuthzConfig) (*PluginAuthz, error) {
glog.Infof("Plugin authorization: %s", cfg)
authz, err := lookupSymbol(cfg)
if err != nil {
return nil, err
}
return &PluginAuthz{Authz: authz}, nil
}

func (c *PluginAuthz) Authorize(ai *AuthRequestInfo) ([]string, error) {
// use the plugin
return c.Authz.Authorize(ai)
}
38 changes: 25 additions & 13 deletions auth_server/server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,19 @@ import (
)

type Config struct {
Server ServerConfig `yaml:"server"`
Token TokenConfig `yaml:"token"`
Users map[string]*authn.Requirements `yaml:"users,omitempty"`
GoogleAuth *authn.GoogleAuthConfig `yaml:"google_auth,omitempty"`
GitHubAuth *authn.GitHubAuthConfig `yaml:"github_auth,omitempty"`
LDAPAuth *authn.LDAPAuthConfig `yaml:"ldap_auth,omitempty"`
MongoAuth *authn.MongoAuthConfig `yaml:"mongo_auth,omitempty"`
ExtAuth *authn.ExtAuthConfig `yaml:"ext_auth,omitempty"`
ACL authz.ACL `yaml:"acl,omitempty"`
ACLMongo *authz.ACLMongoConfig `yaml:"acl_mongo,omitempty"`
ExtAuthz *authz.ExtAuthzConfig `yaml:"ext_authz,omitempty"`
Server ServerConfig `yaml:"server"`
Token TokenConfig `yaml:"token"`
Users map[string]*authn.Requirements `yaml:"users,omitempty"`
GoogleAuth *authn.GoogleAuthConfig `yaml:"google_auth,omitempty"`
GitHubAuth *authn.GitHubAuthConfig `yaml:"github_auth,omitempty"`
LDAPAuth *authn.LDAPAuthConfig `yaml:"ldap_auth,omitempty"`
MongoAuth *authn.MongoAuthConfig `yaml:"mongo_auth,omitempty"`
ExtAuth *authn.ExtAuthConfig `yaml:"ext_auth,omitempty"`
PluginAuthn *authn.PluginAuthnConfig `yaml:"plugin_authn,omitempty"`
ACL authz.ACL `yaml:"acl,omitempty"`
ACLMongo *authz.ACLMongoConfig `yaml:"acl_mongo,omitempty"`
ExtAuthz *authz.ExtAuthzConfig `yaml:"ext_authz,omitempty"`
PluginAuthz *authz.PluginAuthzConfig `yaml:"plugin_authz,omitempty"`
}

type ServerConfig struct {
Expand Down Expand Up @@ -89,7 +91,7 @@ func validate(c *Config) error {
if c.Token.Expiration <= 0 {
return fmt.Errorf("expiration must be positive, got %d", c.Token.Expiration)
}
if c.Users == nil && c.ExtAuth == nil && c.GoogleAuth == nil && c.GitHubAuth == nil && c.LDAPAuth == nil && c.MongoAuth == nil {
if c.Users == nil && c.ExtAuth == nil && c.GoogleAuth == nil && c.GitHubAuth == nil && c.LDAPAuth == nil && c.MongoAuth == nil && c.PluginAuthn == nil {
return errors.New("no auth methods are configured, this is probably a mistake. Use an empty user map if you really want to deny everyone.")
}
if c.MongoAuth != nil {
Expand Down Expand Up @@ -140,7 +142,7 @@ func validate(c *Config) error {
return fmt.Errorf("bad ext_auth config: %s", err)
}
}
if c.ACL == nil && c.ACLMongo == nil && c.ExtAuthz == nil {
if c.ACL == nil && c.ACLMongo == nil && c.ExtAuthz == nil && c.PluginAuthz == nil {
return errors.New("ACL is empty, this is probably a mistake. Use an empty list if you really want to deny all actions")
}

Expand All @@ -159,6 +161,16 @@ func validate(c *Config) error {
return err
}
}
if c.PluginAuthn != nil {
if err := c.PluginAuthn.Validate(); err != nil {
return fmt.Errorf("bad plugin_authn config: %s", err)
}
}
if c.PluginAuthz != nil {
if err := c.PluginAuthz.Validate(); err != nil {
return fmt.Errorf("bad plugin_authz config: %s", err)
}
}
return nil
}

Expand Down
14 changes: 14 additions & 0 deletions auth_server/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,20 @@ func NewAuthServer(c *Config) (*AuthServer, error) {
}
as.authenticators = append(as.authenticators, ma)
}
if c.PluginAuthn != nil {
pluginAuthn, err := authn.NewPluginAuthn(c.PluginAuthn)
if err != nil {
return nil, err
}
as.authenticators = append(as.authenticators, pluginAuthn)
}
if c.PluginAuthz != nil {
pluginAuthz, err := authz.NewPluginAuthzAuthorizer(c.PluginAuthz)
if err != nil {
return nil, err
}
as.authorizers = append(as.authorizers, pluginAuthz)
}
return as, nil
}

Expand Down
14 changes: 14 additions & 0 deletions examples/reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,14 @@ ext_auth:
command: "/usr/local/bin/my_auth" # Can be a relative path too; $PATH works.
args: ["--flag", "--more", "--flags"]

# User written authentication plugin - call a user written program to authenticate user.
# Username of type string and password of authn.PasswordString is passed to the plugin
# Expects a boolean value whether the user is authenticate or not, authn.Labels, error
# The "labels" key may contain labels to be passed down to authz, where they can
# be used in matching.
plugin_authn:
plugin_path: ""

# Authorization methods. All are tried, any one returning success is sufficient.
# At least one must be configured.

Expand Down Expand Up @@ -306,3 +314,9 @@ acl_mongo:
ext_authz:
command: "/usr/local/bin/my_authz" # Can be a relative path too; $PATH works.
args: ["--flag", "--more", "--flags"]

# User written authorization plugin - call a user written program to authorize user.
# *authz.AuthRequestInfo is passed to the plugin and expects an authorized set of actions or an error.
# return the set of authorized actions is the user is authorized. Otherwise return nil
plugin_authz:
plugin_path: ""

0 comments on commit bfb1517

Please sign in to comment.