feat: add API key scopes and application_connect scope (#4067)

This commit is contained in:
Dean Sheather
2022-09-19 17:39:02 +00:00
committed by GitHub
parent adad347902
commit 29d804e692
42 changed files with 476 additions and 88 deletions
@@ -1588,6 +1588,7 @@ func (q *fakeQuerier) InsertAPIKey(_ context.Context, arg database.InsertAPIKeyP
UpdatedAt: arg.UpdatedAt,
LastUsed: arg.LastUsed,
LoginType: arg.LoginType,
Scope: arg.Scope,
}
q.apiKeys = append(q.apiKeys, key)
return key, nil
+18 -1
View File
@@ -4,12 +4,15 @@ package database_test
import (
"context"
"database/sql"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/database/migrations"
"github.com/coder/coder/coderd/database/postgres"
)
func TestNestedInTx(t *testing.T) {
@@ -20,7 +23,7 @@ func TestNestedInTx(t *testing.T) {
uid := uuid.New()
sqlDB := testSQLDB(t)
err := database.MigrateUp(sqlDB)
err := migrations.Up(sqlDB)
require.NoError(t, err, "migrations")
db := database.New(sqlDB)
@@ -48,3 +51,17 @@ func TestNestedInTx(t *testing.T) {
require.NoError(t, err, "user exists")
require.Equal(t, uid, user.ID, "user id expected")
}
func testSQLDB(t testing.TB) *sql.DB {
t.Helper()
connection, closeFn, err := postgres.Open()
require.NoError(t, err)
t.Cleanup(closeFn)
db, err := sql.Open("postgres", connection)
require.NoError(t, err)
t.Cleanup(func() { _ = db.Close() })
return db
}
+7 -1
View File
@@ -1,5 +1,10 @@
-- Code generated by 'make coderd/database/generate'. DO NOT EDIT.
CREATE TYPE api_key_scope AS ENUM (
'all',
'application_connect'
);
CREATE TYPE audit_action AS ENUM (
'create',
'write',
@@ -109,7 +114,8 @@ CREATE TABLE api_keys (
updated_at timestamp with time zone NOT NULL,
login_type login_type NOT NULL,
lifetime_seconds bigint DEFAULT 86400 NOT NULL,
ip_address inet DEFAULT '0.0.0.0'::inet NOT NULL
ip_address inet DEFAULT '0.0.0.0'::inet NOT NULL,
scope api_key_scope DEFAULT 'all'::public.api_key_scope NOT NULL
);
CREATE TABLE audit_logs (
+2 -2
View File
@@ -9,7 +9,7 @@ import (
"path/filepath"
"runtime"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/database/migrations"
"github.com/coder/coder/coderd/database/postgres"
)
@@ -25,7 +25,7 @@ func main() {
panic(err)
}
err = database.MigrateUp(db)
err = migrations.Up(db)
if err != nil {
panic(err)
}
@@ -0,0 +1,6 @@
-- Avoid "upgrading" devurl keys to fully fledged API keys.
DELETE FROM api_keys WHERE scope != 'all';
ALTER TABLE api_keys DROP COLUMN scope;
DROP TYPE api_key_scope;
@@ -0,0 +1,6 @@
CREATE TYPE api_key_scope AS ENUM (
'all',
'application_connect'
);
ALTER TABLE api_keys ADD COLUMN scope api_key_scope NOT NULL DEFAULT 'all';
@@ -1,4 +1,4 @@
package database
package migrations
import (
"context"
@@ -14,12 +14,12 @@ import (
"golang.org/x/xerrors"
)
//go:embed migrations/*.sql
//go:embed *.sql
var migrations embed.FS
func migrateSetup(db *sql.DB) (source.Driver, *migrate.Migrate, error) {
func setup(db *sql.DB) (source.Driver, *migrate.Migrate, error) {
ctx := context.Background()
sourceDriver, err := iofs.New(migrations, "migrations")
sourceDriver, err := iofs.New(migrations, ".")
if err != nil {
return nil, nil, xerrors.Errorf("create iofs: %w", err)
}
@@ -45,9 +45,9 @@ func migrateSetup(db *sql.DB) (source.Driver, *migrate.Migrate, error) {
return sourceDriver, m, nil
}
// MigrateUp runs SQL migrations to ensure the database schema is up-to-date.
func MigrateUp(db *sql.DB) (retErr error) {
_, m, err := migrateSetup(db)
// Up runs SQL migrations to ensure the database schema is up-to-date.
func Up(db *sql.DB) (retErr error) {
_, m, err := setup(db)
if err != nil {
return xerrors.Errorf("migrate setup: %w", err)
}
@@ -76,9 +76,9 @@ func MigrateUp(db *sql.DB) (retErr error) {
return nil
}
// MigrateDown runs all down SQL migrations.
func MigrateDown(db *sql.DB) error {
_, m, err := migrateSetup(db)
// Down runs all down SQL migrations.
func Down(db *sql.DB) error {
_, m, err := setup(db)
if err != nil {
return xerrors.Errorf("migrate setup: %w", err)
}
@@ -100,7 +100,7 @@ func MigrateDown(db *sql.DB) error {
// applied, without making any changes to the database. If not, returns a
// non-nil error.
func EnsureClean(db *sql.DB) error {
sourceDriver, m, err := migrateSetup(db)
sourceDriver, m, err := setup(db)
if err != nil {
return xerrors.Errorf("migrate setup: %w", err)
}
@@ -1,6 +1,6 @@
//go:build linux
package database_test
package migrations_test
import (
"database/sql"
@@ -12,7 +12,7 @@ import (
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/database/migrations"
"github.com/coder/coder/coderd/database/postgres"
)
@@ -33,7 +33,7 @@ func TestMigrate(t *testing.T) {
db := testSQLDB(t)
err := database.MigrateUp(db)
err := migrations.Up(db)
require.NoError(t, err)
})
@@ -42,10 +42,10 @@ func TestMigrate(t *testing.T) {
db := testSQLDB(t)
err := database.MigrateUp(db)
err := migrations.Up(db)
require.NoError(t, err)
err = database.MigrateUp(db)
err = migrations.Up(db)
require.NoError(t, err)
})
@@ -54,13 +54,13 @@ func TestMigrate(t *testing.T) {
db := testSQLDB(t)
err := database.MigrateUp(db)
err := migrations.Up(db)
require.NoError(t, err)
err = database.MigrateDown(db)
err = migrations.Down(db)
require.NoError(t, err)
err = database.MigrateUp(db)
err = migrations.Up(db)
require.NoError(t, err)
})
}
@@ -120,7 +120,7 @@ func TestCheckLatestVersion(t *testing.T) {
})
}
err := database.CheckLatestVersion(driver, tc.currentVersion)
err := migrations.CheckLatestVersion(driver, tc.currentVersion)
var errMessage string
if err != nil {
errMessage = err.Error()
+15
View File
@@ -4,6 +4,17 @@ import (
"github.com/coder/coder/coderd/rbac"
)
func (s APIKeyScope) ToRBAC() rbac.Scope {
switch s {
case APIKeyScopeAll:
return rbac.ScopeAll
case APIKeyScopeApplicationConnect:
return rbac.ScopeApplicationConnect
default:
panic("developer error: unknown scope type " + string(s))
}
}
func (t Template) RBACObject() rbac.Object {
return rbac.ResourceTemplate.InOrg(t.OrganizationID)
}
@@ -21,6 +32,10 @@ func (w Workspace) ExecutionRBAC() rbac.Object {
return rbac.ResourceWorkspaceExecution.InOrg(w.OrganizationID).WithOwner(w.OwnerID.String())
}
func (w Workspace) ApplicationConnectRBAC() rbac.Object {
return rbac.ResourceWorkspaceApplicationConnect.InOrg(w.OrganizationID).WithOwner(w.OwnerID.String())
}
func (m OrganizationMember) RBACObject() rbac.Object {
return rbac.ResourceOrganizationMember.InOrg(m.OrganizationID)
}
+20
View File
@@ -14,6 +14,25 @@ import (
"github.com/tabbed/pqtype"
)
type APIKeyScope string
const (
APIKeyScopeAll APIKeyScope = "all"
APIKeyScopeApplicationConnect APIKeyScope = "application_connect"
)
func (e *APIKeyScope) Scan(src interface{}) error {
switch s := src.(type) {
case []byte:
*e = APIKeyScope(s)
case string:
*e = APIKeyScope(s)
default:
return fmt.Errorf("unsupported scan type for APIKeyScope: %T", src)
}
return nil
}
type AuditAction string
const (
@@ -324,6 +343,7 @@ type APIKey struct {
LoginType LoginType `db:"login_type" json:"login_type"`
LifetimeSeconds int64 `db:"lifetime_seconds" json:"lifetime_seconds"`
IPAddress pqtype.Inet `db:"ip_address" json:"ip_address"`
Scope APIKeyScope `db:"scope" json:"scope"`
}
type AgentStat struct {
+2 -2
View File
@@ -14,7 +14,7 @@ import (
"github.com/ory/dockertest/v3/docker"
"golang.org/x/xerrors"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/database/migrations"
"github.com/coder/coder/cryptorand"
)
@@ -143,7 +143,7 @@ func Open() (string, func(), error) {
return retryErr
}
err = database.MigrateUp(db)
err = migrations.Up(db)
if err != nil {
retryErr = xerrors.Errorf("migrate db: %w", err)
// Only try to migrate once.
+10 -4
View File
@@ -128,7 +128,7 @@ func (q *sqlQuerier) DeleteAPIKeyByID(ctx context.Context, id string) error {
const getAPIKeyByID = `-- name: GetAPIKeyByID :one
SELECT
id, hashed_secret, user_id, last_used, expires_at, created_at, updated_at, login_type, lifetime_seconds, ip_address
id, hashed_secret, user_id, last_used, expires_at, created_at, updated_at, login_type, lifetime_seconds, ip_address, scope
FROM
api_keys
WHERE
@@ -151,12 +151,13 @@ func (q *sqlQuerier) GetAPIKeyByID(ctx context.Context, id string) (APIKey, erro
&i.LoginType,
&i.LifetimeSeconds,
&i.IPAddress,
&i.Scope,
)
return i, err
}
const getAPIKeysLastUsedAfter = `-- name: GetAPIKeysLastUsedAfter :many
SELECT id, hashed_secret, user_id, last_used, expires_at, created_at, updated_at, login_type, lifetime_seconds, ip_address FROM api_keys WHERE last_used > $1
SELECT id, hashed_secret, user_id, last_used, expires_at, created_at, updated_at, login_type, lifetime_seconds, ip_address, scope FROM api_keys WHERE last_used > $1
`
func (q *sqlQuerier) GetAPIKeysLastUsedAfter(ctx context.Context, lastUsed time.Time) ([]APIKey, error) {
@@ -179,6 +180,7 @@ func (q *sqlQuerier) GetAPIKeysLastUsedAfter(ctx context.Context, lastUsed time.
&i.LoginType,
&i.LifetimeSeconds,
&i.IPAddress,
&i.Scope,
); err != nil {
return nil, err
}
@@ -205,7 +207,8 @@ INSERT INTO
expires_at,
created_at,
updated_at,
login_type
login_type,
scope
)
VALUES
($1,
@@ -214,7 +217,7 @@ VALUES
WHEN 0 THEN 86400
ELSE $2::bigint
END
, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id, hashed_secret, user_id, last_used, expires_at, created_at, updated_at, login_type, lifetime_seconds, ip_address
, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id, hashed_secret, user_id, last_used, expires_at, created_at, updated_at, login_type, lifetime_seconds, ip_address, scope
`
type InsertAPIKeyParams struct {
@@ -228,6 +231,7 @@ type InsertAPIKeyParams struct {
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
LoginType LoginType `db:"login_type" json:"login_type"`
Scope APIKeyScope `db:"scope" json:"scope"`
}
func (q *sqlQuerier) InsertAPIKey(ctx context.Context, arg InsertAPIKeyParams) (APIKey, error) {
@@ -242,6 +246,7 @@ func (q *sqlQuerier) InsertAPIKey(ctx context.Context, arg InsertAPIKeyParams) (
arg.CreatedAt,
arg.UpdatedAt,
arg.LoginType,
arg.Scope,
)
var i APIKey
err := row.Scan(
@@ -255,6 +260,7 @@ func (q *sqlQuerier) InsertAPIKey(ctx context.Context, arg InsertAPIKeyParams) (
&i.LoginType,
&i.LifetimeSeconds,
&i.IPAddress,
&i.Scope,
)
return i, err
}
+3 -2
View File
@@ -23,7 +23,8 @@ INSERT INTO
expires_at,
created_at,
updated_at,
login_type
login_type,
scope
)
VALUES
(@id,
@@ -32,7 +33,7 @@ VALUES
WHEN 0 THEN 86400
ELSE @lifetime_seconds::bigint
END
, @hashed_secret, @ip_address, @user_id, @last_used, @expires_at, @created_at, @updated_at, @login_type) RETURNING *;
, @hashed_secret, @ip_address, @user_id, @last_used, @expires_at, @created_at, @updated_at, @login_type, @scope) RETURNING *;
-- name: UpdateAPIKeyByID :exec
UPDATE
+3
View File
@@ -18,6 +18,9 @@ packages:
rename:
api_key: APIKey
api_key_scope: APIKeyScope
api_key_scope_all: APIKeyScopeAll
api_key_scope_application_connect: APIKeyScopeApplicationConnect
avatar_url: AvatarURL
login_type_oidc: LoginTypeOIDC
oauth_access_token: OAuthAccessToken