mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 17:05:18 +00:00
SAML Database Support
Partially addresses #1456. This PR provides datastore support for SSO by creating a new entity IdentityProvider. This entity is an abstraction of the SAML IdentityProvider and contains the data needed to perform SAML authentication.
This commit is contained in:
parent
40610e508f
commit
789596a78e
81
server/datastore/datastore_identity_providers_test.go
Normal file
81
server/datastore/datastore_identity_providers_test.go
Normal file
@ -0,0 +1,81 @@
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kolide/kolide/server/kolide"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func testIdentityProvider(t *testing.T, ds kolide.Datastore) {
|
||||
if ds.Name() == "inmem" {
|
||||
t.Skip("imem is being deprecated")
|
||||
}
|
||||
idps := []*kolide.IdentityProvider{
|
||||
&kolide.IdentityProvider{
|
||||
SingleSignOnURL: "https://idp1.com/sso",
|
||||
IssuerURI: "http://idp1.com/issuer/xyz123",
|
||||
Certificate: "DEADBEEFXXXXX12344",
|
||||
Name: "idp1",
|
||||
ImageURL: "https://idp1.com/logo.png",
|
||||
},
|
||||
&kolide.IdentityProvider{
|
||||
SingleSignOnURL: "https://idp2.com/sso",
|
||||
IssuerURI: "http://idp2.com/issuer/xyz123",
|
||||
Certificate: "DEADBEEFXXXXX12344",
|
||||
Name: "idp2",
|
||||
ImageURL: "https://idp2.com/logo.png",
|
||||
},
|
||||
&kolide.IdentityProvider{
|
||||
SingleSignOnURL: "https://idp3.com/sso",
|
||||
IssuerURI: "http://idp3.com/issuer/xyz123",
|
||||
Certificate: "DEADBEEFXXXXX12344",
|
||||
Name: "idp3",
|
||||
ImageURL: "https://idp3.com/logo.png",
|
||||
},
|
||||
}
|
||||
var err error
|
||||
for i, idp := range idps {
|
||||
idps[i], err = ds.NewIdentityProvider(*idp)
|
||||
require.Nil(t, err)
|
||||
require.NotEqual(t, 0, idp.ID, "id assignment")
|
||||
}
|
||||
// duplicate name not allowed
|
||||
_, err = ds.NewIdentityProvider(*idps[0])
|
||||
assert.NotNil(t, err)
|
||||
// test get
|
||||
idp, err := ds.IdentityProvider(idps[0].ID)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, idp)
|
||||
require.Equal(t, "idp1", idp.Name)
|
||||
// test update
|
||||
idp.ImageURL = "https://idpnew.com/logo.png"
|
||||
idp.SingleSignOnURL = "https://idpnew.com/sso"
|
||||
idp.IssuerURI = "https://idpnew.com/issuer"
|
||||
idp.Certificate = "123456789"
|
||||
idp.Name = "idpnew"
|
||||
err = ds.SaveIdentityProvider(*idp)
|
||||
require.Nil(t, err)
|
||||
upd, err := ds.IdentityProvider(idp.ID)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, upd)
|
||||
assert.Equal(t, idp.ImageURL, upd.ImageURL)
|
||||
assert.Equal(t, idp.SingleSignOnURL, upd.SingleSignOnURL)
|
||||
assert.Equal(t, idp.IssuerURI, upd.IssuerURI)
|
||||
assert.Equal(t, idp.Certificate, upd.Certificate)
|
||||
assert.Equal(t, idp.Name, upd.Name)
|
||||
// test list
|
||||
results, err := ds.ListIdentityProviders()
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, results)
|
||||
assert.Len(t, results, 3)
|
||||
// test delete
|
||||
err = ds.DeleteIdentityProvider(results[0].ID)
|
||||
assert.Nil(t, err)
|
||||
err = ds.DeleteIdentityProvider(results[0].ID)
|
||||
assert.NotNil(t, err)
|
||||
results, err = ds.ListIdentityProviders()
|
||||
require.Nil(t, err)
|
||||
assert.NotNil(t, results, 2)
|
||||
}
|
@ -78,4 +78,5 @@ var testFunctions = [...]func(*testing.T, kolide.Datastore){
|
||||
testUnicode,
|
||||
testCountHostsInTargets,
|
||||
testResetOptions,
|
||||
testIdentityProvider,
|
||||
}
|
||||
|
25
server/datastore/inmem/identity_providers.go
Normal file
25
server/datastore/inmem/identity_providers.go
Normal file
@ -0,0 +1,25 @@
|
||||
package inmem
|
||||
|
||||
import (
|
||||
"github.com/kolide/kolide/server/kolide"
|
||||
)
|
||||
|
||||
func (d *Datastore) NewIdentityProvider(idp kolide.IdentityProvider) (*kolide.IdentityProvider, error) {
|
||||
panic("inmem is being deprecated")
|
||||
}
|
||||
|
||||
func (d *Datastore) SaveIdentityProvider(idb kolide.IdentityProvider) error {
|
||||
panic("inmem is being deprecated")
|
||||
}
|
||||
|
||||
func (d *Datastore) IdentityProvider(id uint) (*kolide.IdentityProvider, error) {
|
||||
panic("inmem is being deprecated")
|
||||
}
|
||||
|
||||
func (d *Datastore) DeleteIdentityProvider(id uint) error {
|
||||
panic("inmem is being deprecated")
|
||||
}
|
||||
|
||||
func (d *Datastore) ListIdentityProviders() ([]kolide.IdentityProvider, error) {
|
||||
panic("inmem is being deprecated")
|
||||
}
|
91
server/datastore/mysql/identity_providers.go
Normal file
91
server/datastore/mysql/identity_providers.go
Normal file
@ -0,0 +1,91 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/kolide/kolide/server/kolide"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (d *Datastore) NewIdentityProvider(idp kolide.IdentityProvider) (*kolide.IdentityProvider, error) {
|
||||
query := `
|
||||
INSERT INTO identity_providers (
|
||||
sso_url,
|
||||
issuer_uri,
|
||||
cert,
|
||||
name,
|
||||
image_url
|
||||
)
|
||||
VALUES ( ?, ?, ?, ?, ? )
|
||||
`
|
||||
result, err := d.db.Exec(query, idp.SingleSignOnURL, idp.IssuerURI, idp.Certificate, idp.Name, idp.ImageURL)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating identity provider")
|
||||
}
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "retrieving id for new identity provider")
|
||||
}
|
||||
idp.ID = uint(id)
|
||||
return &idp, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) SaveIdentityProvider(idp kolide.IdentityProvider) error {
|
||||
query := `
|
||||
UPDATE identity_providers
|
||||
SET
|
||||
sso_url = ?,
|
||||
issuer_uri = ?,
|
||||
cert = ?,
|
||||
name = ?,
|
||||
image_url = ?
|
||||
WHERE id = ?
|
||||
`
|
||||
result, err := d.db.Exec(query, idp.SingleSignOnURL, idp.IssuerURI, idp.Certificate,
|
||||
idp.Name, idp.ImageURL, idp.ID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "updating identity provider")
|
||||
}
|
||||
rows, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "fetching updated row count for identity provider")
|
||||
}
|
||||
if rows == 0 {
|
||||
return notFound("IdentityProvider").WithID(idp.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Datastore) IdentityProvider(id uint) (*kolide.IdentityProvider, error) {
|
||||
query := `
|
||||
SELECT *
|
||||
FROM identity_providers
|
||||
WHERE id = ? AND NOT deleted
|
||||
`
|
||||
var idp kolide.IdentityProvider
|
||||
err := d.db.Get(&idp, query, id)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, notFound("IdentityProvider").WithID(id)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "selecting identity provider")
|
||||
}
|
||||
return &idp, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) DeleteIdentityProvider(id uint) error {
|
||||
return d.deleteEntity("identity_providers", id)
|
||||
}
|
||||
|
||||
func (d *Datastore) ListIdentityProviders() ([]kolide.IdentityProvider, error) {
|
||||
query := `
|
||||
SELECT *
|
||||
FROM identity_providers
|
||||
WHERE NOT deleted
|
||||
`
|
||||
var idps []kolide.IdentityProvider
|
||||
if err := d.db.Select(&idps, query); err != nil {
|
||||
return nil, errors.Wrap(err, "listing identity providers")
|
||||
}
|
||||
return idps, nil
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package tables
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
func init() {
|
||||
MigrationClient.AddMigration(Up_20170411155225, Down_20170411155225)
|
||||
}
|
||||
|
||||
func Up_20170411155225(tx *sql.Tx) error {
|
||||
statement :=
|
||||
"CREATE TABLE `identity_providers` ( " +
|
||||
"`id` int(11) NOT NULL AUTO_INCREMENT, " +
|
||||
"`sso_url` varchar(1024) NOT NULL DEFAULT '', " +
|
||||
"`issuer_uri` varchar(1024) NOT NULL DEFAULT '', " +
|
||||
"`cert` text NOT NULL, " +
|
||||
"`name` varchar(128) NOT NULL DEFAULT '', " +
|
||||
"`image_url` varchar(1024) NOT NULL DEFAULT '', " +
|
||||
"`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
|
||||
"`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
|
||||
"`deleted_at` timestamp NULL DEFAULT NULL, " +
|
||||
"`deleted` tinyint(1) NOT NULL DEFAULT FALSE, " +
|
||||
"PRIMARY KEY (`id`), " +
|
||||
"UNIQUE KEY `idx_unique_identity_providers_name` (`name`) USING BTREE " +
|
||||
") ENGINE=InnoDB DEFAULT CHARSET=utf8;"
|
||||
_, err := tx.Exec(statement)
|
||||
return err
|
||||
}
|
||||
|
||||
func Down_20170411155225(tx *sql.Tx) error {
|
||||
_, err := tx.Exec("DROP TABLE IF EXISTS `identity_providers`;")
|
||||
return err
|
||||
}
|
@ -19,6 +19,7 @@ type Datastore interface {
|
||||
FileIntegrityMonitoringStore
|
||||
YARAStore
|
||||
LicenseStore
|
||||
IdentityProviderStore
|
||||
Name() string
|
||||
Drop() error
|
||||
// MigrateTables creates and migrates the table schemas
|
||||
|
62
server/kolide/identity_providers.go
Normal file
62
server/kolide/identity_providers.go
Normal file
@ -0,0 +1,62 @@
|
||||
package kolide
|
||||
|
||||
import "context"
|
||||
|
||||
// IdentityProviderStore exposes methods to persist IdentityProviders.
|
||||
// IdentityProvider is an entity used for single sign on.
|
||||
type IdentityProviderStore interface {
|
||||
// NewIdentityProvider creates a new IdentityProvider.
|
||||
NewIdentityProvider(idp IdentityProvider) (*IdentityProvider, error)
|
||||
// SaveIdentityProvider saves changes to an IdentityProvider.
|
||||
SaveIdentityProvider(idb IdentityProvider) error
|
||||
// IdentityProvider retrieves an IdentityProvider identified by id.
|
||||
IdentityProvider(id uint) (*IdentityProvider, error)
|
||||
// DeleteIdentityProvider soft deletes an IdentityProvider
|
||||
DeleteIdentityProvider(id uint) error
|
||||
// ListIdentityProviders returns all IdentityProvider entities
|
||||
ListIdentityProviders() ([]IdentityProvider, error)
|
||||
}
|
||||
|
||||
// IdentityProvider represents a SAML identity provider.
|
||||
type IdentityProvider struct {
|
||||
UpdateCreateTimestamps
|
||||
DeleteFields
|
||||
ID uint `json:"id"`
|
||||
// SingleSignOnURL is the URL for the identity provider.
|
||||
SingleSignOnURL string `json:"sso_url" db:"sso_url"`
|
||||
// IssuerURI identity provider issuer
|
||||
IssuerURI string `json:"issuer_uri" db:"issuer_uri"`
|
||||
// Certificate is the identity provider's public certificate.
|
||||
Certificate string `json:"cert" db:"cert"`
|
||||
// Name is the descriptive name for the identity provider that will
|
||||
// be displayed in the UI.
|
||||
Name string `json:"name"`
|
||||
// ImageURL is a link to an icon that will be displayed on the SSO
|
||||
// button for a particular identity provider.
|
||||
ImageURL string `json:"image_url" db:"image_url"`
|
||||
}
|
||||
|
||||
// IdentityProviderPayload user to update one or more fields of an IdentityProvider
|
||||
// by supplying values that correspond to fields that will be changed.
|
||||
type IdentityProviderPayload struct {
|
||||
SingleSignOnURL *string `json:"sso_url"`
|
||||
IssuerURI *string `json:"issuer_uri"`
|
||||
Certificate *string `json:"cert"`
|
||||
Name *string `json:"name"`
|
||||
ImageURL *string `json:"image_url"`
|
||||
}
|
||||
|
||||
// IdentityProviderService exposes methods to manage IdentityProvider entities
|
||||
type IdentityProviderService interface {
|
||||
// NewIdentityProvider creates a IdentityProvider
|
||||
NewIdentityProvider(ctx context.Context, payload IdentityProviderPayload) (*IdentityProvider, error)
|
||||
// SaveIdentityProvider is used to modify an existing IdentityProvider. Nonnil
|
||||
// fields in the payload argument will be changed for an existing IdentityProvider
|
||||
ModifyIdentityProvider(ctx context.Context, id uint, payload IdentityProviderPayload) (*IdentityProvider, error)
|
||||
// GetIdentityProvider retrieves an IdentityProvider given it's ID.
|
||||
GetIdentityProvider(ctx context.Context, id uint) (*IdentityProvider, error)
|
||||
// DeleteIdentityProvider removes an IdentityProvider
|
||||
DeleteIdentityProvider(ctx context.Context, id uint) error
|
||||
// ListIdentityProviders returns a list of all IdentityProvider entities
|
||||
ListIdentityProviders(ctx context.Context, id uint) ([]IdentityProvider, error)
|
||||
}
|
@ -33,6 +33,9 @@ type SessionStore interface {
|
||||
}
|
||||
|
||||
type SessionService interface {
|
||||
// SSOLogin handles creating a session for a user who is authenticated by
|
||||
// a SAML identity provider, returning the user and a token on success
|
||||
SSOLogin(ctx context.Context, userId string) (*User, string, error)
|
||||
Login(ctx context.Context, username, password string) (user *User, token string, err error)
|
||||
Logout(ctx context.Context) (err error)
|
||||
DestroySession(ctx context.Context) (err error)
|
||||
|
@ -9,6 +9,7 @@ package mock
|
||||
//go:generate mockimpl -o datastore_options.go "s *OptionStore" "kolide.OptionStore"
|
||||
//go:generate mockimpl -o datastore_packs.go "s *PackStore" "kolide.PackStore"
|
||||
//go:generate mockimpl -o datastore_hosts.go "s *HostStore" "kolide.HostStore"
|
||||
//go:generate mockimpl -o datastore_identity_providers.go "s *IdentityProviderStore" "kolide.IdentityProviderStore"
|
||||
|
||||
import "github.com/kolide/kolide/server/kolide"
|
||||
|
||||
@ -32,6 +33,7 @@ type Store struct {
|
||||
OptionStore
|
||||
PackStore
|
||||
UserStore
|
||||
IdentityProviderStore
|
||||
}
|
||||
|
||||
func (m *Store) Drop() error {
|
||||
|
59
server/mock/datastore_identity_providers.go
Normal file
59
server/mock/datastore_identity_providers.go
Normal file
@ -0,0 +1,59 @@
|
||||
// Automatically generated by mockimpl. DO NOT EDIT!
|
||||
|
||||
package mock
|
||||
|
||||
import "github.com/kolide/kolide/server/kolide"
|
||||
|
||||
var _ kolide.IdentityProviderStore = (*IdentityProviderStore)(nil)
|
||||
|
||||
type NewIdentityProviderFunc func(idp kolide.IdentityProvider) (*kolide.IdentityProvider, error)
|
||||
|
||||
type SaveIdentityProviderFunc func(idb kolide.IdentityProvider) error
|
||||
|
||||
type IdentityProviderFunc func(id uint) (*kolide.IdentityProvider, error)
|
||||
|
||||
type DeleteIdentityProviderFunc func(id uint) error
|
||||
|
||||
type ListIdentityProvidersFunc func() ([]kolide.IdentityProvider, error)
|
||||
|
||||
type IdentityProviderStore struct {
|
||||
NewIdentityProviderFunc NewIdentityProviderFunc
|
||||
NewIdentityProviderFuncInvoked bool
|
||||
|
||||
SaveIdentityProviderFunc SaveIdentityProviderFunc
|
||||
SaveIdentityProviderFuncInvoked bool
|
||||
|
||||
IdentityProviderFunc IdentityProviderFunc
|
||||
IdentityProviderFuncInvoked bool
|
||||
|
||||
DeleteIdentityProviderFunc DeleteIdentityProviderFunc
|
||||
DeleteIdentityProviderFuncInvoked bool
|
||||
|
||||
ListIdentityProvidersFunc ListIdentityProvidersFunc
|
||||
ListIdentityProvidersFuncInvoked bool
|
||||
}
|
||||
|
||||
func (s *IdentityProviderStore) NewIdentityProvider(idp kolide.IdentityProvider) (*kolide.IdentityProvider, error) {
|
||||
s.NewIdentityProviderFuncInvoked = true
|
||||
return s.NewIdentityProviderFunc(idp)
|
||||
}
|
||||
|
||||
func (s *IdentityProviderStore) SaveIdentityProvider(idb kolide.IdentityProvider) error {
|
||||
s.SaveIdentityProviderFuncInvoked = true
|
||||
return s.SaveIdentityProviderFunc(idb)
|
||||
}
|
||||
|
||||
func (s *IdentityProviderStore) IdentityProvider(id uint) (*kolide.IdentityProvider, error) {
|
||||
s.IdentityProviderFuncInvoked = true
|
||||
return s.IdentityProviderFunc(id)
|
||||
}
|
||||
|
||||
func (s *IdentityProviderStore) DeleteIdentityProvider(id uint) error {
|
||||
s.DeleteIdentityProviderFuncInvoked = true
|
||||
return s.DeleteIdentityProviderFunc(id)
|
||||
}
|
||||
|
||||
func (s *IdentityProviderStore) ListIdentityProviders() ([]kolide.IdentityProvider, error) {
|
||||
s.ListIdentityProvidersFuncInvoked = true
|
||||
return s.ListIdentityProvidersFunc()
|
||||
}
|
@ -13,6 +13,10 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (svc service) SSOLogin(ctx context.Context, userId string) (*kolide.User, string, error) {
|
||||
return nil, "", errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (svc service) Login(ctx context.Context, username, password string) (*kolide.User, string, error) {
|
||||
user, err := svc.userByEmailOrUsername(username)
|
||||
if _, ok := err.(kolide.NotFoundError); ok {
|
||||
|
Loading…
Reference in New Issue
Block a user