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,
|
testUnicode,
|
||||||
testCountHostsInTargets,
|
testCountHostsInTargets,
|
||||||
testResetOptions,
|
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
|
FileIntegrityMonitoringStore
|
||||||
YARAStore
|
YARAStore
|
||||||
LicenseStore
|
LicenseStore
|
||||||
|
IdentityProviderStore
|
||||||
Name() string
|
Name() string
|
||||||
Drop() error
|
Drop() error
|
||||||
// MigrateTables creates and migrates the table schemas
|
// 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 {
|
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)
|
Login(ctx context.Context, username, password string) (user *User, token string, err error)
|
||||||
Logout(ctx context.Context) (err error)
|
Logout(ctx context.Context) (err error)
|
||||||
DestroySession(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_options.go "s *OptionStore" "kolide.OptionStore"
|
||||||
//go:generate mockimpl -o datastore_packs.go "s *PackStore" "kolide.PackStore"
|
//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_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"
|
import "github.com/kolide/kolide/server/kolide"
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ type Store struct {
|
|||||||
OptionStore
|
OptionStore
|
||||||
PackStore
|
PackStore
|
||||||
UserStore
|
UserStore
|
||||||
|
IdentityProviderStore
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Store) Drop() error {
|
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"
|
"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) {
|
func (svc service) Login(ctx context.Context, username, password string) (*kolide.User, string, error) {
|
||||||
user, err := svc.userByEmailOrUsername(username)
|
user, err := svc.userByEmailOrUsername(username)
|
||||||
if _, ok := err.(kolide.NotFoundError); ok {
|
if _, ok := err.(kolide.NotFoundError); ok {
|
||||||
|
Loading…
Reference in New Issue
Block a user