Merge branch 'tmessi-wh-credentials'

pull/1539/head
Timothy Messier 5 years ago
commit 21e61d362e
No known key found for this signature in database
GPG Key ID: EFD2F184F7600572

@ -0,0 +1,129 @@
begin;
-- replaces check from internal/db/schema/migrations/postgres/0/60_wh_domain_types.up.sql
alter domain wh_public_id drop constraint wh_public_id_check;
alter domain wh_public_id add constraint wh_public_id_check
check(
value = 'None'
or
value = 'Unknown'
or
length(trim(value)) > 10
);
create table wh_credential_dimension (
-- random id generated using encode(digest(gen_random_bytes(16), 'sha256'), 'base64')
-- this is done to prevent conflicts with rows in other clusters
-- which enables warehouse data from multiple clusters to be loaded into a
-- single database instance
key wh_dim_key primary key default wh_dim_key(),
credential_purpose wh_dim_text,
credential_library_id wh_public_id not null,
credential_library_type wh_dim_text,
credential_library_name wh_dim_text,
credential_library_description wh_dim_text,
credential_library_vault_path wh_dim_text,
credential_library_vault_http_method wh_dim_text,
credential_library_vault_http_request_body wh_dim_text,
credential_store_id wh_public_id not null,
credential_store_type wh_dim_text,
credential_store_name wh_dim_text,
credential_store_description wh_dim_text,
credential_store_vault_namespace wh_dim_text,
credential_store_vault_address wh_dim_text,
target_id wh_public_id not null,
target_type wh_dim_text,
target_name wh_dim_text,
target_description wh_dim_text,
target_default_port_number integer not null,
target_session_max_seconds integer not null,
target_session_connection_limit integer not null,
project_id wt_scope_id not null,
project_name wh_dim_text,
project_description wh_dim_text,
organization_id wt_scope_id not null,
organization_name wh_dim_text,
organization_description wh_dim_text,
current_row_indicator wh_dim_text,
row_effective_time wh_timestamp,
row_expiration_time wh_timestamp
);
-- https://www.postgresql.org/docs/current/indexes-partial.html
create unique index wh_credential_dim_current_constraint
on wh_credential_dimension (credential_library_id, credential_store_id, target_id, credential_purpose)
where current_row_indicator = 'Current';
-- One part of a bridge table to associated the set of wh_credential_dimension with a fact table.
-- The other part of the bridge is wh_credential_group_membership.
create table wh_credential_group (
-- random id generated using encode(digest(gen_random_bytes(16), 'sha256'), 'base64')
-- this is done to prevent conflicts with rows in other clusters
-- which enables warehouse data from multiple clusters to be loaded into a
-- single database instance
key wh_dim_key primary key default wh_dim_key()
);
-- The second part of the bridge table. The other part is wh_credential_group.
create table wh_credential_group_membership (
credential_group_key wh_dim_key not null
references wh_credential_group (key)
on delete restrict
on update cascade,
credential_key wh_dim_key not null
references wh_credential_dimension (key)
on delete restrict
on update cascade
);
-- Add "no credentials" and "Unknown" group an dimension.
-- When a session has no credentials "no credentials" is used as the "None" value.
-- "Unknown" is used for existing data prior to the credential_dimension existing.
insert into wh_credential_group
(key)
values
('no credentials'),
('Unknown');
insert into wh_credential_dimension (
key,
credential_purpose,
credential_library_id, credential_library_type, credential_library_name, credential_library_description, credential_library_vault_path, credential_library_vault_http_method, credential_library_vault_http_request_body,
credential_store_id, credential_store_type, credential_store_name, credential_store_description, credential_store_vault_namespace, credential_store_vault_address,
target_id, target_type, target_name, target_description, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id, project_name, project_description,
organization_id, organization_name, organization_description,
current_row_indicator, row_effective_time, row_expiration_time
)
values
(
'no credential',
'None',
'None', 'None', 'None', 'None', 'None', 'None', 'None',
'None', 'None', 'None', 'None', 'None', 'None',
'None', 'None', 'None', 'None', -1, -1, -1,
'00000000000', 'None', 'None',
'00000000000', 'None', 'None',
'Current', now(), 'infinity'::timestamptz
),
(
'Unknown',
'Unknown',
'Unknown', 'Unknown', 'Unknown', 'Unknown', 'Unknown', 'Unknown', 'Unknown',
'Unknown', 'Unknown', 'Unknown', 'Unknown', 'Unknown', 'Unknown',
'Unknown', 'Unknown', 'Unknown', 'Unknown', -1, -1, -1,
'00000000000', 'Unknown', 'Unknown',
'00000000000', 'Unknown', 'Unknown',
'Current', now(), 'infinity'::timestamptz
);
insert into wh_credential_group_membership
(credential_group_key, credential_key)
values
('no credentials', 'no credential'),
('Unknown', 'Unknown');
commit;

@ -0,0 +1,100 @@
begin;
-- The whx_credential_dimension_source and whx_credential_dimension_target views are used
-- by an insert trigger to determine if the current row for the dimension has
-- changed and a new one needs to be inserted. The first column in the target view
-- must be the current warehouse id and all remaining columns must match the columns
-- in the source view.
-- The whx_credential_dimension_source view shows the current values in the
-- operational tables of the credential dimension.
create view whx_credential_dimension_source as
select -- id is the first column in the target view
s.public_id as session_id,
coalesce(scd.credential_purpose, 'None') as credential_purpose,
cl.public_id as credential_library_id,
case
when vcl is null then 'None'
else 'vault credential library'
end as credential_library_type,
coalesce(vcl.name, 'None') as credential_library_name,
coalesce(vcl.description, 'None') as credential_library_description,
coalesce(vcl.vault_path, 'None') as credential_library_vault_path,
coalesce(vcl.http_method, 'None') as credential_library_vault_http_method,
coalesce(vcl.http_request_body, 'None') as credential_library_vault_http_request_body,
cs.public_id as credential_store_id,
case
when vcs is null then 'None'
else 'vault credential store'
end as credential_store_type,
coalesce(vcs.name, 'None') as credential_store_name,
coalesce(vcs.description, 'None') as credential_store_description,
coalesce(vcs.namespace, 'None') as credential_store_vault_namespace,
coalesce(vcs.vault_address, 'None') as credential_store_vault_address,
t.public_id as target_id,
'tcp target' as target_type,
coalesce(tt.name, 'None') as target_name,
coalesce(tt.description, 'None') as target_description,
coalesce(tt.default_port, 0) as target_default_port_number,
tt.session_max_seconds as target_session_max_seconds,
tt.session_connection_limit as target_session_connection_limit,
p.public_id as project_id,
coalesce(p.name, 'None') as project_name,
coalesce(p.description, 'None') as project_description,
o.public_id as organization_id,
coalesce(o.name, 'None') as organization_name,
coalesce(o.description, 'None') as organization_description
from session_credential_dynamic as scd,
session as s,
credential_library as cl,
credential_store as cs,
credential_vault_library as vcl,
credential_vault_store as vcs,
target as t,
target_tcp as tt,
iam_scope as p,
iam_scope as o
where scd.library_id = cl.public_id
and cl.store_id = cs.public_id
and vcl.public_id = cl.public_id
and vcs.public_id = cs.public_id
and s.public_id = scd.session_id
and s.target_id = t.public_id
and t.public_id = tt.public_id
and p.public_id = t.scope_id
and p.type = 'project'
and o.public_id = p.parent_id
and o.type = 'org';
create view whx_credential_dimension_target as
select key,
credential_purpose,
credential_library_id,
credential_library_type,
credential_library_name,
credential_library_description,
credential_library_vault_path,
credential_library_vault_http_method,
credential_library_vault_http_request_body,
credential_store_id,
credential_store_type,
credential_store_name,
credential_store_description,
credential_store_vault_namespace,
credential_store_vault_address,
target_id,
target_type,
target_name,
target_description,
target_default_port_number,
target_session_max_seconds,
target_session_connection_limit,
project_id,
project_name,
project_description,
organization_id,
organization_name,
organization_description
from wh_credential_dimension
where current_row_indicator = 'Current'
;
commit;

@ -0,0 +1,170 @@
begin;
-- wh_upsert_credential_dimension compares the current vaules in the wh_credential_dimension
-- with the current values in the operational tables for the given parameters. IF the values
-- between operational tables and the wh_credential_dimension differ, a new row is inserted in
-- the wh_credential_dimension to match the current values in the operational tables.
create function wh_upsert_credential_dimension(p_session_id wt_public_id, p_library_id wt_public_id, p_credential_purpose wh_dim_text)
returns wh_dim_key
as $$
declare
src whx_credential_dimension_target%rowtype;
target whx_credential_dimension_target%rowtype;
new_row wh_credential_dimension%rowtype;
t_id wt_public_id;
begin
select s.target_id into strict t_id
from session as s
where s.public_id = p_session_id;
select * into target
from whx_credential_dimension_target as t
where t.credential_library_id = p_library_id
and t.target_id = t_id
and t.credential_purpose = p_credential_purpose;
select
target.key, t.credential_purpose,
t.credential_library_id, t.credential_library_type, t.credential_library_name, t.credential_library_description, t.credential_library_vault_path, t.credential_library_vault_http_method, t.credential_library_vault_http_request_body,
t.credential_store_id, t.credential_store_type, t.credential_store_name, t.credential_store_description, t.credential_store_vault_namespace, t.credential_store_vault_address,
t.target_id, t.target_type, t.target_name, t.target_description, t.target_default_port_number, t.target_session_max_seconds, t.target_session_connection_limit,
t.project_id, t.project_name, t.project_description,
t.organization_id, t.organization_name, t.organization_description
into src
from whx_credential_dimension_source as t
where t.credential_library_id = p_library_id
and t.session_id = p_session_id
and t.target_id = t_id
and t.credential_purpose = p_credential_purpose;
if src is distinct from target then
update wh_credential_dimension
set current_row_indicator = 'Expired',
row_expiration_time = current_timestamp
where credential_library_id = p_library_id
and target_id = t_id
and credential_purpose = p_credential_purpose
and current_row_indicator = 'Current';
insert into wh_credential_dimension (
credential_purpose,
credential_library_id, credential_library_type, credential_library_name, credential_library_description, credential_library_vault_path, credential_library_vault_http_method, credential_library_vault_http_request_body,
credential_store_id, credential_store_type, credential_store_name, credential_store_description, credential_store_vault_namespace, credential_store_vault_address,
target_id, target_type, target_name, target_description, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id, project_name, project_description,
organization_id, organization_name, organization_description,
current_row_indicator, row_effective_time, row_expiration_time
)
select credential_purpose,
credential_library_id, credential_library_type, credential_library_name, credential_library_description, credential_library_vault_path, credential_library_vault_http_method, credential_library_vault_http_request_body,
credential_store_id, credential_store_type, credential_store_name, credential_store_description, credential_store_vault_namespace, credential_store_vault_address,
target_id, target_type, target_name, target_description, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id, project_name, project_description,
organization_id, organization_name, organization_description,
'Current', current_timestamp, 'infinity'::timestamptz
from whx_credential_dimension_source
where credential_library_id = p_library_id
and session_id = p_session_id
and target_id = t_id
and credential_purpose = p_credential_purpose
returning * into new_row;
return new_row.key;
end if;
return target.key;
end
$$ language plpgsql;
-- Run wh_upsert_credential_dimension for session_credential_dynamic row that is inserted.
create function wh_insert_session_credential_dynamic()
returns trigger
as $$
begin
perform wh_upsert_credential_dimension(new.session_id, new.library_id, new.credential_purpose);
return null;
end;
$$ language plpgsql;
create trigger wh_insert_session_credential_dynamic
after insert on session_credential_dynamic
for each row
execute function wh_insert_session_credential_dynamic();
-- wh_upsert_credentail_group determines if a new wh_credential_group needs to be
-- created due to changes to the coresponding wh_credential_dimensions. It then
-- updates the wh_session_accumulating_fact to associate it with the correct wh_credential_group.
create function wh_upsert_credentail_group()
returns trigger
as $$
declare
cg_key wh_dim_key;
t_id wt_public_id;
s_id wt_public_id;
c_key wh_dim_key;
begin
select distinct scd.session_id into strict s_id
from new_table as scd;
select distinct s.target_id into strict t_id
from new_table as scd
left join session as s on s.public_id = scd.session_id;
-- based on query written by Michele Gaffney
with
credential_list (key) as (
select key
from wh_credential_dimension
where target_id = t_id
and credential_library_id in (select credential_library_id from new_table)
)
select distinct credential_group_key into cg_key
from wh_credential_group_membership a
where a.credential_key in (select key from credential_list)
and (select count(key) from credential_list) =
(
select count(b.credential_key)
from wh_credential_group_membership b
where a.credential_key = b.credential_key
and b.credential_key in (select key from credential_list)
)
and not exists
(
select 1
from wh_credential_group_membership b
where a.credential_key = b.credential_key
and b.credential_key not in (select key from credential_list)
)
;
if cg_key is null then
insert into wh_credential_group default values returning key into cg_key;
for c_key in
select key
from wh_credential_dimension
where target_id = t_id
and credential_library_id in (select credential_library_id from new_table)
loop
insert into wh_credential_group_membership
(credential_group_key, credential_key)
values
(cg_key, c_key);
end loop;
end if;
update wh_session_connection_accumulating_fact
set credential_group_key = cg_key
where session_id = s_id;
return null;
end;
$$ language plpgsql;
-- Run wh_upsert_credentail_group on statement. This assumes that all relevant
-- session_credential_dynamic rows are inserted as a single statement and that
-- the wh_insert_session_credential_dynamic trigger ran for each row and updated
-- the wh_credential_dimensions. Then this statement trigger can run to update the
-- bridge tables and wh_session_accumulating_fact.
create trigger wh_insert_stmt_session_credential_dynamic
after insert on session_credential_dynamic
referencing new table as new_table
for each statement
execute function wh_upsert_credentail_group();
commit;

@ -0,0 +1,54 @@
begin;
alter table wh_session_accumulating_fact
add column credential_group_key wh_dim_key not null
default 'Unknown'
references wh_credential_group (key)
on delete restrict
on update cascade;
alter table wh_session_accumulating_fact
alter column credential_group_key drop default;
-- replaces function from 15/01_wh_rename_key_columns.up.sql
drop trigger wh_insert_session on session;
drop function wh_insert_session;
create function wh_insert_session()
returns trigger
as $$
declare
new_row wh_session_accumulating_fact%rowtype;
begin
with
pending_timestamp (date_dim_key, time_dim_key, ts) as (
select wh_date_key(start_time), wh_time_key(start_time), start_time
from session_state
where session_id = new.public_id
and state = 'pending'
)
insert into wh_session_accumulating_fact (
session_id,
auth_token_id,
host_key,
user_key,
credential_group_key,
session_pending_date_key,
session_pending_time_key,
session_pending_time
)
select new.public_id,
new.auth_token_id,
wh_upsert_host(new.host_id, new.host_set_id, new.target_id),
wh_upsert_user(new.user_id, new.auth_token_id),
'no credentials', -- will be updated by wh_upsert_credentail_group
pending_timestamp.date_dim_key,
pending_timestamp.time_dim_key,
pending_timestamp.ts
from pending_timestamp
returning * into strict new_row;
return null;
end;
$$ language plpgsql;
create trigger wh_insert_session
after insert on session
for each row
execute function wh_insert_session();
commit;

@ -0,0 +1,75 @@
begin;
alter table wh_session_connection_accumulating_fact
add column credential_group_key wh_dim_key not null
default 'Unknown'
references wh_credential_group (key)
on delete restrict
on update cascade;
alter table wh_session_connection_accumulating_fact
alter column credential_group_key drop default;
-- replaces function from 15/01_wh_rename_key_columns.up.sql
drop trigger wh_insert_session_connection on session_connection;
drop function wh_insert_session_connection;
create function wh_insert_session_connection()
returns trigger
as $$
declare
new_row wh_session_connection_accumulating_fact%rowtype;
begin
with
authorized_timestamp (date_dim_key, time_dim_key, ts) as (
select wh_date_key(start_time), wh_time_key(start_time), start_time
from session_connection_state
where connection_id = new.public_id
and state = 'authorized'
),
session_dimension (host_dim_key, user_dim_key, credential_group_dim_key) as (
select host_key, user_key, credential_group_key
from wh_session_accumulating_fact
where session_id = new.session_id
)
insert into wh_session_connection_accumulating_fact (
connection_id,
session_id,
host_key,
user_key,
credential_group_key,
connection_authorized_date_key,
connection_authorized_time_key,
connection_authorized_time,
client_tcp_address,
client_tcp_port_number,
endpoint_tcp_address,
endpoint_tcp_port_number,
bytes_up,
bytes_down
)
select new.public_id,
new.session_id,
session_dimension.host_dim_key,
session_dimension.user_dim_key,
session_dimension.credential_group_dim_key,
authorized_timestamp.date_dim_key,
authorized_timestamp.time_dim_key,
authorized_timestamp.ts,
new.client_tcp_address,
new.client_tcp_port,
new.endpoint_tcp_address,
new.endpoint_tcp_port,
new.bytes_up,
new.bytes_down
from authorized_timestamp,
session_dimension
returning * into strict new_row;
perform wh_rollup_connections(new.session_id);
return null;
end;
$$ language plpgsql;
create trigger wh_insert_session_connection
after insert on session_connection
for each row
execute function wh_insert_session_connection();
commit;

@ -0,0 +1,189 @@
package migration
import (
"context"
"database/sql"
"testing"
"github.com/hashicorp/boundary/internal/auth/oidc"
"github.com/hashicorp/boundary/internal/authtoken"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/vault"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/schema"
"github.com/hashicorp/boundary/internal/host/static"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/session"
"github.com/hashicorp/boundary/internal/target"
"github.com/hashicorp/boundary/testing/dbtest"
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMigrations_CredentialDimension(t *testing.T) {
const (
priorMigration = 15002
currentMigration = 16005
)
t.Parallel()
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
dialect := dbtest.Postgres
c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1))
require.NoError(err)
t.Cleanup(func() {
require.NoError(c())
})
d, err := sql.Open(dialect, u)
require.NoError(err)
// migration to the prior migration (before the one we want to test)
oState := schema.TestCloneMigrationStates(t)
nState := schema.TestCreatePartialMigrationState(oState["postgres"], priorMigration)
oState["postgres"] = nState
m, err := schema.NewManager(ctx, dialect, d, schema.WithMigrationStates(oState))
require.NoError(err)
assert.NoError(m.RollForward(ctx))
state, err := m.CurrentState(ctx)
require.NoError(err)
assert.Equal(priorMigration, state.DatabaseSchemaVersion)
assert.False(state.Dirty)
// okay, now we can seed the database with test data
conn, err := gorm.Open(dialect, u)
require.NoError(err)
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
require.NotNil(prj)
assert.NotEmpty(prj.GetPublicId())
hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0]
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()}))
var sessions []*session.Session
kmsCache := kms.TestKms(t, conn, wrapper)
databaseWrapper, err := kmsCache.GetWrapper(ctx, org.GetPublicId(), kms.KeyPurposeDatabase)
require.NoError(err)
{
at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId())
uId := at.GetIamUserId()
sess := session.TestSession(t, conn, wrapper, session.ComposedOf{
UserId: uId,
HostId: h.GetPublicId(),
TargetId: tar.GetPublicId(),
HostSetId: hs.GetPublicId(),
AuthTokenId: at.GetPublicId(),
ScopeId: prj.GetPublicId(),
Endpoint: "tcp://127.0.0.1:22",
})
sessions = append(sessions, sess)
}
{
at := testOidcAuthToken(t, conn, kmsCache, databaseWrapper, org.GetPublicId())
uId := at.GetIamUserId()
creds := testSessionCredentialParams(t, conn, kmsCache, wrapper, tar)
sess := session.TestSession(t, conn, wrapper, session.ComposedOf{
UserId: uId,
HostId: h.GetPublicId(),
TargetId: tar.GetPublicId(),
HostSetId: hs.GetPublicId(),
AuthTokenId: at.GetPublicId(),
ScopeId: prj.GetPublicId(),
Endpoint: "tcp://127.0.0.1:22",
DynamicCredentials: creds,
})
sessions = append(sessions, sess)
}
sessionRepo, err := session.NewRepository(rw, rw, kmsCache)
require.NoError(err)
count, err := sessionRepo.TerminateCompletedSessions(ctx)
assert.NoError(err)
assert.Zero(count)
for _, sess := range sessions {
// call TerminateSession
_, err = sessionRepo.TerminateSession(ctx, sess.GetPublicId(), 1, session.ClosedByUser)
assert.NoError(err)
}
// now we're ready for the migration we want to test.
oState = schema.TestCloneMigrationStates(t)
nState = schema.TestCreatePartialMigrationState(oState["postgres"], currentMigration)
oState["postgres"] = nState
m, err = schema.NewManager(ctx, dialect, d, schema.WithMigrationStates(oState))
require.NoError(err)
assert.NoError(m.RollForward(ctx))
state, err = m.CurrentState(ctx)
require.NoError(err)
assert.Equal(currentMigration, state.DatabaseSchemaVersion)
assert.False(state.Dirty)
}
func testOidcAuthToken(t *testing.T, conn *gorm.DB, kms *kms.Kms, wrapper wrapping.Wrapper, scopeId string) *authtoken.AuthToken {
t.Helper()
authMethod := oidc.TestAuthMethod(
t, conn, wrapper, scopeId, oidc.ActivePrivateState,
"alice-rp", "fido",
oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://www.alice.com")[0]),
oidc.WithSigningAlgs(oidc.RS256),
oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://www.alice.com/callback")[0]),
)
acct := oidc.TestAccount(t, conn, authMethod, "test-subject")
ctx := context.Background()
rw := db.New(conn)
iamRepo, err := iam.NewRepository(rw, rw, kms)
require.NoError(t, err)
u := iam.TestUser(t, iamRepo, scopeId, iam.WithAccountIds(acct.PublicId))
repo, err := authtoken.NewRepository(rw, rw, kms)
require.NoError(t, err)
at, err := repo.CreateAuthToken(ctx, u, acct.GetPublicId())
require.NoError(t, err)
return at
}
func testSessionCredentialParams(t *testing.T, conn *gorm.DB, kms *kms.Kms, wrapper wrapping.Wrapper, tar *target.TcpTarget) []*session.DynamicCredential {
t.Helper()
rw := db.New(conn)
ctx := context.Background()
stores := vault.TestCredentialStores(t, conn, wrapper, tar.ScopeId, 1)
libs := vault.TestCredentialLibraries(t, conn, wrapper, stores[0].GetPublicId(), 2)
targetRepo, err := target.NewRepository(rw, rw, kms)
require.NoError(t, err)
_, _, _, err = targetRepo.AddTargetCredentialSources(ctx, tar.GetPublicId(), tar.GetVersion(), []string{libs[0].PublicId, libs[1].PublicId})
require.NoError(t, err)
creds := []*session.DynamicCredential{
session.NewDynamicCredential(libs[0].GetPublicId(), credential.ApplicationPurpose),
session.NewDynamicCredential(libs[0].GetPublicId(), credential.IngressPurpose),
session.NewDynamicCredential(libs[1].GetPublicId(), credential.EgressPurpose),
}
return creds
}

@ -4,7 +4,7 @@ package schema
func init() {
migrationStates["postgres"] = migrationState{
binarySchemaVersion: 15002,
binarySchemaVersion: 16005,
upMigrations: map[int][]byte{
1: []byte(`
create domain wt_public_id as text
@ -6938,6 +6938,532 @@ alter table wh_host_dimension
end;
$$ language plpgsql;
`),
16001: []byte(`
-- replaces check from internal/db/schema/migrations/postgres/0/60_wh_domain_types.up.sql
alter domain wh_public_id drop constraint wh_public_id_check;
alter domain wh_public_id add constraint wh_public_id_check
check(
value = 'None'
or
value = 'Unknown'
or
length(trim(value)) > 10
);
create table wh_credential_dimension (
-- random id generated using encode(digest(gen_random_bytes(16), 'sha256'), 'base64')
-- this is done to prevent conflicts with rows in other clusters
-- which enables warehouse data from multiple clusters to be loaded into a
-- single database instance
key wh_dim_key primary key default wh_dim_key(),
credential_purpose wh_dim_text,
credential_library_id wh_public_id not null,
credential_library_type wh_dim_text,
credential_library_name wh_dim_text,
credential_library_description wh_dim_text,
credential_library_vault_path wh_dim_text,
credential_library_vault_http_method wh_dim_text,
credential_library_vault_http_request_body wh_dim_text,
credential_store_id wh_public_id not null,
credential_store_type wh_dim_text,
credential_store_name wh_dim_text,
credential_store_description wh_dim_text,
credential_store_vault_namespace wh_dim_text,
credential_store_vault_address wh_dim_text,
target_id wh_public_id not null,
target_type wh_dim_text,
target_name wh_dim_text,
target_description wh_dim_text,
target_default_port_number integer not null,
target_session_max_seconds integer not null,
target_session_connection_limit integer not null,
project_id wt_scope_id not null,
project_name wh_dim_text,
project_description wh_dim_text,
organization_id wt_scope_id not null,
organization_name wh_dim_text,
organization_description wh_dim_text,
current_row_indicator wh_dim_text,
row_effective_time wh_timestamp,
row_expiration_time wh_timestamp
);
-- https://www.postgresql.org/docs/current/indexes-partial.html
create unique index wh_credential_dim_current_constraint
on wh_credential_dimension (credential_library_id, credential_store_id, target_id, credential_purpose)
where current_row_indicator = 'Current';
-- One part of a bridge table to associated the set of wh_credential_dimension with a fact table.
-- The other part of the bridge is wh_credential_group_membership.
create table wh_credential_group (
-- random id generated using encode(digest(gen_random_bytes(16), 'sha256'), 'base64')
-- this is done to prevent conflicts with rows in other clusters
-- which enables warehouse data from multiple clusters to be loaded into a
-- single database instance
key wh_dim_key primary key default wh_dim_key()
);
-- The second part of the bridge table. The other part is wh_credential_group.
create table wh_credential_group_membership (
credential_group_key wh_dim_key not null
references wh_credential_group (key)
on delete restrict
on update cascade,
credential_key wh_dim_key not null
references wh_credential_dimension (key)
on delete restrict
on update cascade
);
-- Add "no credentials" and "Unknown" group an dimension.
-- When a session has no credentials "no credentials" is used as the "None" value.
-- "Unknown" is used for existing data prior to the credential_dimension existing.
insert into wh_credential_group
(key)
values
('no credentials'),
('Unknown');
insert into wh_credential_dimension (
key,
credential_purpose,
credential_library_id, credential_library_type, credential_library_name, credential_library_description, credential_library_vault_path, credential_library_vault_http_method, credential_library_vault_http_request_body,
credential_store_id, credential_store_type, credential_store_name, credential_store_description, credential_store_vault_namespace, credential_store_vault_address,
target_id, target_type, target_name, target_description, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id, project_name, project_description,
organization_id, organization_name, organization_description,
current_row_indicator, row_effective_time, row_expiration_time
)
values
(
'no credential',
'None',
'None', 'None', 'None', 'None', 'None', 'None', 'None',
'None', 'None', 'None', 'None', 'None', 'None',
'None', 'None', 'None', 'None', -1, -1, -1,
'00000000000', 'None', 'None',
'00000000000', 'None', 'None',
'Current', now(), 'infinity'::timestamptz
),
(
'Unknown',
'Unknown',
'Unknown', 'Unknown', 'Unknown', 'Unknown', 'Unknown', 'Unknown', 'Unknown',
'Unknown', 'Unknown', 'Unknown', 'Unknown', 'Unknown', 'Unknown',
'Unknown', 'Unknown', 'Unknown', 'Unknown', -1, -1, -1,
'00000000000', 'Unknown', 'Unknown',
'00000000000', 'Unknown', 'Unknown',
'Current', now(), 'infinity'::timestamptz
);
insert into wh_credential_group_membership
(credential_group_key, credential_key)
values
('no credentials', 'no credential'),
('Unknown', 'Unknown');
`),
16002: []byte(`
-- The whx_credential_dimension_source and whx_credential_dimension_target views are used
-- by an insert trigger to determine if the current row for the dimension has
-- changed and a new one needs to be inserted. The first column in the target view
-- must be the current warehouse id and all remaining columns must match the columns
-- in the source view.
-- The whx_credential_dimension_source view shows the current values in the
-- operational tables of the credential dimension.
create view whx_credential_dimension_source as
select -- id is the first column in the target view
s.public_id as session_id,
coalesce(scd.credential_purpose, 'None') as credential_purpose,
cl.public_id as credential_library_id,
case
when vcl is null then 'None'
else 'vault credential library'
end as credential_library_type,
coalesce(vcl.name, 'None') as credential_library_name,
coalesce(vcl.description, 'None') as credential_library_description,
coalesce(vcl.vault_path, 'None') as credential_library_vault_path,
coalesce(vcl.http_method, 'None') as credential_library_vault_http_method,
coalesce(vcl.http_request_body, 'None') as credential_library_vault_http_request_body,
cs.public_id as credential_store_id,
case
when vcs is null then 'None'
else 'vault credential store'
end as credential_store_type,
coalesce(vcs.name, 'None') as credential_store_name,
coalesce(vcs.description, 'None') as credential_store_description,
coalesce(vcs.namespace, 'None') as credential_store_vault_namespace,
coalesce(vcs.vault_address, 'None') as credential_store_vault_address,
t.public_id as target_id,
'tcp target' as target_type,
coalesce(tt.name, 'None') as target_name,
coalesce(tt.description, 'None') as target_description,
coalesce(tt.default_port, 0) as target_default_port_number,
tt.session_max_seconds as target_session_max_seconds,
tt.session_connection_limit as target_session_connection_limit,
p.public_id as project_id,
coalesce(p.name, 'None') as project_name,
coalesce(p.description, 'None') as project_description,
o.public_id as organization_id,
coalesce(o.name, 'None') as organization_name,
coalesce(o.description, 'None') as organization_description
from session_credential_dynamic as scd,
session as s,
credential_library as cl,
credential_store as cs,
credential_vault_library as vcl,
credential_vault_store as vcs,
target as t,
target_tcp as tt,
iam_scope as p,
iam_scope as o
where scd.library_id = cl.public_id
and cl.store_id = cs.public_id
and vcl.public_id = cl.public_id
and vcs.public_id = cs.public_id
and s.public_id = scd.session_id
and s.target_id = t.public_id
and t.public_id = tt.public_id
and p.public_id = t.scope_id
and p.type = 'project'
and o.public_id = p.parent_id
and o.type = 'org';
create view whx_credential_dimension_target as
select key,
credential_purpose,
credential_library_id,
credential_library_type,
credential_library_name,
credential_library_description,
credential_library_vault_path,
credential_library_vault_http_method,
credential_library_vault_http_request_body,
credential_store_id,
credential_store_type,
credential_store_name,
credential_store_description,
credential_store_vault_namespace,
credential_store_vault_address,
target_id,
target_type,
target_name,
target_description,
target_default_port_number,
target_session_max_seconds,
target_session_connection_limit,
project_id,
project_name,
project_description,
organization_id,
organization_name,
organization_description
from wh_credential_dimension
where current_row_indicator = 'Current'
;
`),
16003: []byte(`
-- wh_upsert_credential_dimension compares the current vaules in the wh_credential_dimension
-- with the current values in the operational tables for the given parameters. IF the values
-- between operational tables and the wh_credential_dimension differ, a new row is inserted in
-- the wh_credential_dimension to match the current values in the operational tables.
create function wh_upsert_credential_dimension(p_session_id wt_public_id, p_library_id wt_public_id, p_credential_purpose wh_dim_text)
returns wh_dim_key
as $$
declare
src whx_credential_dimension_target%rowtype;
target whx_credential_dimension_target%rowtype;
new_row wh_credential_dimension%rowtype;
t_id wt_public_id;
begin
select s.target_id into strict t_id
from session as s
where s.public_id = p_session_id;
select * into target
from whx_credential_dimension_target as t
where t.credential_library_id = p_library_id
and t.target_id = t_id
and t.credential_purpose = p_credential_purpose;
select
target.key, t.credential_purpose,
t.credential_library_id, t.credential_library_type, t.credential_library_name, t.credential_library_description, t.credential_library_vault_path, t.credential_library_vault_http_method, t.credential_library_vault_http_request_body,
t.credential_store_id, t.credential_store_type, t.credential_store_name, t.credential_store_description, t.credential_store_vault_namespace, t.credential_store_vault_address,
t.target_id, t.target_type, t.target_name, t.target_description, t.target_default_port_number, t.target_session_max_seconds, t.target_session_connection_limit,
t.project_id, t.project_name, t.project_description,
t.organization_id, t.organization_name, t.organization_description
into src
from whx_credential_dimension_source as t
where t.credential_library_id = p_library_id
and t.session_id = p_session_id
and t.target_id = t_id
and t.credential_purpose = p_credential_purpose;
if src is distinct from target then
update wh_credential_dimension
set current_row_indicator = 'Expired',
row_expiration_time = current_timestamp
where credential_library_id = p_library_id
and target_id = t_id
and credential_purpose = p_credential_purpose
and current_row_indicator = 'Current';
insert into wh_credential_dimension (
credential_purpose,
credential_library_id, credential_library_type, credential_library_name, credential_library_description, credential_library_vault_path, credential_library_vault_http_method, credential_library_vault_http_request_body,
credential_store_id, credential_store_type, credential_store_name, credential_store_description, credential_store_vault_namespace, credential_store_vault_address,
target_id, target_type, target_name, target_description, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id, project_name, project_description,
organization_id, organization_name, organization_description,
current_row_indicator, row_effective_time, row_expiration_time
)
select credential_purpose,
credential_library_id, credential_library_type, credential_library_name, credential_library_description, credential_library_vault_path, credential_library_vault_http_method, credential_library_vault_http_request_body,
credential_store_id, credential_store_type, credential_store_name, credential_store_description, credential_store_vault_namespace, credential_store_vault_address,
target_id, target_type, target_name, target_description, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id, project_name, project_description,
organization_id, organization_name, organization_description,
'Current', current_timestamp, 'infinity'::timestamptz
from whx_credential_dimension_source
where credential_library_id = p_library_id
and session_id = p_session_id
and target_id = t_id
and credential_purpose = p_credential_purpose
returning * into new_row;
return new_row.key;
end if;
return target.key;
end
$$ language plpgsql;
-- Run wh_upsert_credential_dimension for session_credential_dynamic row that is inserted.
create function wh_insert_session_credential_dynamic()
returns trigger
as $$
begin
perform wh_upsert_credential_dimension(new.session_id, new.library_id, new.credential_purpose);
return null;
end;
$$ language plpgsql;
create trigger wh_insert_session_credential_dynamic
after insert on session_credential_dynamic
for each row
execute function wh_insert_session_credential_dynamic();
-- wh_upsert_credentail_group determines if a new wh_credential_group needs to be
-- created due to changes to the coresponding wh_credential_dimensions. It then
-- updates the wh_session_accumulating_fact to associate it with the correct wh_credential_group.
create function wh_upsert_credentail_group()
returns trigger
as $$
declare
cg_key wh_dim_key;
t_id wt_public_id;
s_id wt_public_id;
c_key wh_dim_key;
begin
select distinct scd.session_id into strict s_id
from new_table as scd;
select distinct s.target_id into strict t_id
from new_table as scd
left join session as s on s.public_id = scd.session_id;
-- based on query written by Michele Gaffney
with
credential_list (key) as (
select key
from wh_credential_dimension
where target_id = t_id
and credential_library_id in (select credential_library_id from new_table)
)
select distinct credential_group_key into cg_key
from wh_credential_group_membership a
where a.credential_key in (select key from credential_list)
and (select count(key) from credential_list) =
(
select count(b.credential_key)
from wh_credential_group_membership b
where a.credential_key = b.credential_key
and b.credential_key in (select key from credential_list)
)
and not exists
(
select 1
from wh_credential_group_membership b
where a.credential_key = b.credential_key
and b.credential_key not in (select key from credential_list)
)
;
if cg_key is null then
insert into wh_credential_group default values returning key into cg_key;
for c_key in
select key
from wh_credential_dimension
where target_id = t_id
and credential_library_id in (select credential_library_id from new_table)
loop
insert into wh_credential_group_membership
(credential_group_key, credential_key)
values
(cg_key, c_key);
end loop;
end if;
update wh_session_connection_accumulating_fact
set credential_group_key = cg_key
where session_id = s_id;
return null;
end;
$$ language plpgsql;
-- Run wh_upsert_credentail_group on statement. This assumes that all relevant
-- session_credential_dynamic rows are inserted as a single statement and that
-- the wh_insert_session_credential_dynamic trigger ran for each row and updated
-- the wh_credential_dimensions. Then this statement trigger can run to update the
-- bridge tables and wh_session_accumulating_fact.
create trigger wh_insert_stmt_session_credential_dynamic
after insert on session_credential_dynamic
referencing new table as new_table
for each statement
execute function wh_upsert_credentail_group();
`),
16004: []byte(`
alter table wh_session_accumulating_fact
add column credential_group_key wh_dim_key not null
default 'Unknown'
references wh_credential_group (key)
on delete restrict
on update cascade;
alter table wh_session_accumulating_fact
alter column credential_group_key drop default;
-- replaces function from 15/01_wh_rename_key_columns.up.sql
drop trigger wh_insert_session on session;
drop function wh_insert_session;
create function wh_insert_session()
returns trigger
as $$
declare
new_row wh_session_accumulating_fact%rowtype;
begin
with
pending_timestamp (date_dim_key, time_dim_key, ts) as (
select wh_date_key(start_time), wh_time_key(start_time), start_time
from session_state
where session_id = new.public_id
and state = 'pending'
)
insert into wh_session_accumulating_fact (
session_id,
auth_token_id,
host_key,
user_key,
credential_group_key,
session_pending_date_key,
session_pending_time_key,
session_pending_time
)
select new.public_id,
new.auth_token_id,
wh_upsert_host(new.host_id, new.host_set_id, new.target_id),
wh_upsert_user(new.user_id, new.auth_token_id),
'no credentials', -- will be updated by wh_upsert_credentail_group
pending_timestamp.date_dim_key,
pending_timestamp.time_dim_key,
pending_timestamp.ts
from pending_timestamp
returning * into strict new_row;
return null;
end;
$$ language plpgsql;
create trigger wh_insert_session
after insert on session
for each row
execute function wh_insert_session();
`),
16005: []byte(`
alter table wh_session_connection_accumulating_fact
add column credential_group_key wh_dim_key not null
default 'Unknown'
references wh_credential_group (key)
on delete restrict
on update cascade;
alter table wh_session_connection_accumulating_fact
alter column credential_group_key drop default;
-- replaces function from 15/01_wh_rename_key_columns.up.sql
drop trigger wh_insert_session_connection on session_connection;
drop function wh_insert_session_connection;
create function wh_insert_session_connection()
returns trigger
as $$
declare
new_row wh_session_connection_accumulating_fact%rowtype;
begin
with
authorized_timestamp (date_dim_key, time_dim_key, ts) as (
select wh_date_key(start_time), wh_time_key(start_time), start_time
from session_connection_state
where connection_id = new.public_id
and state = 'authorized'
),
session_dimension (host_dim_key, user_dim_key, credential_group_dim_key) as (
select host_key, user_key, credential_group_key
from wh_session_accumulating_fact
where session_id = new.session_id
)
insert into wh_session_connection_accumulating_fact (
connection_id,
session_id,
host_key,
user_key,
credential_group_key,
connection_authorized_date_key,
connection_authorized_time_key,
connection_authorized_time,
client_tcp_address,
client_tcp_port_number,
endpoint_tcp_address,
endpoint_tcp_port_number,
bytes_up,
bytes_down
)
select new.public_id,
new.session_id,
session_dimension.host_dim_key,
session_dimension.user_dim_key,
session_dimension.credential_group_dim_key,
authorized_timestamp.date_dim_key,
authorized_timestamp.time_dim_key,
authorized_timestamp.ts,
new.client_tcp_address,
new.client_tcp_port,
new.endpoint_tcp_address,
new.endpoint_tcp_port,
new.bytes_up,
new.bytes_down
from authorized_timestamp,
session_dimension
returning * into strict new_row;
perform wh_rollup_connections(new.session_id);
return null;
end;
$$ language plpgsql;
create trigger wh_insert_session_connection
after insert on session_connection
for each row
execute function wh_insert_session_connection();
`),
2001: []byte(`
-- log_migration entries represent logs generated during migrations

@ -175,10 +175,26 @@ begin;
('t_________cr', 's___1cr-sths'),
('t_________cr', 's___2cr-sths');
insert into credential_vault_store
(scope_id, public_id, name, description, vault_address, namespace)
values
('p____bcolors', 'vs_______cvs', 'color vault store', 'None', 'https://vault.color', 'blue');
insert into credential_vault_library
(store_id, public_id, name, description, vault_path, http_method)
values
('vs_______cvs', 'vl______cvl', 'color vault library', 'None', '/secrets', 'GET');
insert into target_credential_library
(target_id, credential_library_id, credential_purpose)
values
('t_________cb', 'vl______cvl', 'application');
insert into session
( scope_id , target_id , host_set_id , host_id , user_id , auth_token_id , certificate , endpoint , public_id)
( scope_id, target_id, host_set_id, host_id, user_id, auth_token_id, certificate, endpoint, public_id)
values
('p____bcolors' , 't_________cb' , 's___1cb-sths' , 'h_____cb__01' , 'u______clare' , 'tok____clare' , 'abc'::bytea , 'ep1' , 's1_____clare');
('p____bcolors', 't_________cb', 's___1cb-sths', 'h_____cb__01', 'u______clare', 'tok____clare', 'abc'::bytea, 'ep1', 's1_____clare');
insert into session_connection
(session_id, public_id)

@ -261,5 +261,31 @@ begin;
end;
$$ language plpgsql;
create function _wtt_load_widgets_credentials()
returns void
as $$
begin
insert into credential_vault_store
(scope_id, public_id, name, description, vault_address, namespace)
values
('p____bwidget', 'vs_______wvs', 'widget vault store', 'None', 'https://vault.widget', 'default');
insert into credential_vault_library
(store_id, public_id, name, description, vault_path, http_method)
values
('vs_______wvs', 'vl______wvl1', 'widget vault library', 'None', '/secrets', 'GET'),
('vs_______wvs', 'vl______wvl2', 'widget vault ssh', 'None', '/secrets/ssh/admin', 'GET'),
('vs_______wvs', 'vl______wvl3', 'widget vault kv', 'None', '/secrets/kv', 'GET');
insert into target_credential_library
(target_id, credential_library_id, credential_purpose)
values
('t_________wb', 'vl______wvl1', 'application'),
('t_________wb', 'vl______wvl2', 'application'),
('t_________wb', 'vl______wvl3', 'application'),
('t_________wb', 'vl______wvl3', 'egress');
end;
$$ language plpgsql;
commit;

@ -0,0 +1,53 @@
begin;
select plan(2);
insert into wh_credential_dimension (
credential_purpose,
credential_library_id, credential_library_type, credential_library_name, credential_library_description, credential_library_vault_path, credential_library_vault_http_method, credential_library_vault_http_request_body,
credential_store_id, credential_store_type, credential_store_name, credential_store_description, credential_store_vault_namespace, credential_store_vault_address,
target_id, target_type, target_name, target_description, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id, project_name, project_description,
organization_id, organization_name, organization_description,
current_row_indicator, row_effective_time, row_expiration_time
) values (
'application',
'vl______wvl1', 'vault credential library', 'gidget vault library', 'None', '/secrets', 'GET', '\x4e6f6e65',
'vs_______wvs', 'vault credential store', 'widget vault store', 'None', 'default', 'https://vault.widget',
't_________wb', 'tcp target', 'Big Widget Target', 'None', 0, 28800, 1,
'p____bwidget', 'Big Widget Factory', 'None',
'o_____widget', 'Widget Inc', 'None',
'Current', current_timestamp, 'infinity'::timestamptz
);
update wh_credential_dimension
set current_row_indicator = 'Expired',
row_expiration_time = current_timestamp
where credential_library_id = 'vl______wvl1'
and credential_store_id = 'vs_______wvs'
and target_id = 't_________wb'
and credential_purpose = 'application'
and current_row_indicator = 'Current';
insert into wh_credential_dimension (
credential_purpose,
credential_library_id, credential_library_type, credential_library_name, credential_library_description, credential_library_vault_path, credential_library_vault_http_method, credential_library_vault_http_request_body,
credential_store_id, credential_store_type, credential_store_name, credential_store_description, credential_store_vault_namespace, credential_store_vault_address,
target_id, target_type, target_name, target_description, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id, project_name, project_description,
organization_id, organization_name, organization_description,
current_row_indicator, row_effective_time, row_expiration_time
) values (
'application',
'vl______wvl1', 'vault credential library', 'gidget vault library', 'None', '/secrets', 'GET', '\x4e6f6e65',
'vs_______wvs', 'vault credential store', 'widget vault store', 'None', 'default', 'https://vault.widget',
't_________wb', 'tcp target', 'Big Widget Target', 'None', 0, 28800, 1,
'p____bwidget', 'Big Widget Factory', 'None',
'o_____widget', 'Widget Inc', 'None',
'Current', current_timestamp, 'infinity'::timestamptz
);
select is(count(*), 2::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
select is(count(*), 1::bigint) from wh_credential_dimension where organization_id = 'o_____widget' and current_row_indicator = 'Current';
select * from finish();
rollback;

@ -0,0 +1,108 @@
-- session_multiple_sessions tests the wh_credential_dimension when
-- multiple sessions are created using.
begin;
select plan(13);
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials');
-- ensure no existing dimensions
select is(count(*), 0::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
-- insert first session, should result in a new credentials dimension
insert into session
( scope_id, target_id, host_set_id, host_id, user_id, auth_token_id, certificate, endpoint, public_id)
values
('p____bwidget', 't_________wb', 's___1wb-sths', 'h_____wb__01', 'u_____walter', 'tok___walter', 'abc'::bytea, 'ep1', 's1____walter');
insert into session_credential_dynamic
( session_id, library_id, credential_id, credential_purpose)
values
('s1____walter', 'vl______wvl1', null, 'application');
select is(count(*), 1::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
-- another session with:
-- * same user
-- * same auth
-- * same host
-- should not result in a new credential dimension
insert into session
( scope_id, target_id, host_set_id, host_id, user_id, auth_token_id, certificate, endpoint, public_id)
values
('p____bwidget', 't_________wb', 's___1wb-sths', 'h_____wb__01', 'u_____walter', 'tok___walter', 'abc'::bytea, 'ep1', 's2____walter');
insert into session_credential_dynamic
( session_id, library_id, credential_id, credential_purpose)
values
('s2____walter', 'vl______wvl1', null, 'application');
select is(count(*), 1::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
-- change the crediential for the target
update credential_vault_library set vault_path = '/secrets/tcp/admin' where public_id = 'vl______wvl1';
-- start another session, should result in a new credential dimension
insert into session
( scope_id, target_id, host_set_id, host_id, user_id, auth_token_id, certificate, endpoint, public_id)
values
('p____bwidget', 't_________wb', 's___1wb-sths', 'h_____wb__01', 'u_____walter', 'tok___walter', 'abc'::bytea, 'ep1', 's3____walter');
insert into session_credential_dynamic
( session_id, library_id, credential_id, credential_purpose)
values
('s3____walter', 'vl______wvl1', null, 'application');
select is(count(*), 2::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
-- start another session, should result in a one new credential dimensions
insert into session
( scope_id, target_id, host_set_id, host_id, user_id, auth_token_id, certificate, endpoint, public_id)
values
('p____bwidget', 't_________wb', 's___1wb-sths', 'h_____wb__01', 'u_____walter', 'tok___walter', 'abc'::bytea, 'ep1', 's4____walter');
insert into session_credential_dynamic
( session_id, library_id, credential_id, credential_purpose)
values
('s4____walter', 'vl______wvl1', null, 'application'),
('s4____walter', 'vl______wvl2', null, 'application');
select is(count(*), 3::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
-- change the crediential again for the target
update credential_vault_library set vault_path = '/secrets/tcp/user' where vault_path = '/secrets/tcp/admin';
-- start another session, should result in a one new credential dimensions since one changed
insert into session
( scope_id, target_id, host_set_id, host_id, user_id, auth_token_id, certificate, endpoint, public_id)
values
('p____bwidget', 't_________wb', 's___1wb-sths', 'h_____wb__01', 'u_____walter', 'tok___walter', 'abc'::bytea, 'ep1', 's5____walter');
insert into session_credential_dynamic
( session_id, library_id, credential_id, credential_purpose)
values
('s5____walter', 'vl______wvl1', null, 'application'),
('s5____walter', 'vl______wvl2', null, 'application');
select is(count(*), 4::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
select is(count(*), 2::bigint) from wh_credential_dimension where organization_id = 'o_____widget' and current_row_indicator = 'Current';
-- remove all credentials from the target
-- then test creating a session
delete from credential_vault_library;
insert into session
( scope_id, target_id, host_set_id, host_id, user_id, auth_token_id, certificate, endpoint, public_id)
values
('p____bwidget', 't_________wb', 's___1wb-sths', 'h_____wb__01', 'u_____walter', 'tok___walter', 'abc'::bytea, 'ep1', 's6____walter');
select is(count(*), 4::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
select is(credential_group_key, 'no credentials') from wh_session_accumulating_fact where session_id = 's6____walter';
insert into session_connection
(session_id, public_id)
values
('s6____walter', 'sc6____walter');
select is(credential_group_key, 'no credentials') from wh_session_connection_accumulating_fact where session_id = 's6____walter';
-- insert into a session for a target that never had any credentials associated with it.
insert into session
( scope_id, target_id, host_set_id, host_id, user_id, auth_token_id, certificate, endpoint, public_id)
values
('p____bwidget', 't_________ws', 's___1ws-sths', 'h_____ws__01', 'u_____walter', 'tok___walter', 'abc'::bytea, 'ep1', 's7____walter');
select is(count(*), 4::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
select is(credential_group_key, 'no credentials') from wh_session_accumulating_fact where session_id = 's7____walter';
insert into session_connection
(session_id, public_id)
values
('s7____walter', 'sc7____walter');
select is(credential_group_key, 'no credentials') from wh_session_connection_accumulating_fact where session_id = 's7____walter';
select * from finish();
rollback;

@ -0,0 +1,33 @@
-- session_update tests the wh_credential_dimension when
-- a session is inserted and then updated.
begin;
select plan(3);
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials');
-- ensure no existing dimensions
select is(count(*), 0::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
-- insert first session, should result in a new user dimension
insert into session
( scope_id, target_id, host_set_id, host_id, user_id, auth_token_id, certificate, endpoint, public_id)
values
('p____bwidget', 't_________wb', 's___1wb-sths', 'h_____wb__01', 'u_____walter', 'tok___walter', 'abc'::bytea, 'ep1', 's1____walter');
insert into session_credential_dynamic
( session_id, library_id, credential_id, credential_purpose)
values
('s1____walter', 'vl______wvl1', null, 'application');
select is(count(*), 1::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
-- update session, should not impact wh_credential_dimension
update session set
version = 2
where
public_id = 's1____walter';
select is(count(*), 1::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
select * from finish();
rollback;

@ -0,0 +1,47 @@
-- three_credentials_one_change tests that:
-- when a session with three credentials is created
-- three wh_credential_dimensions are created
-- then when one of the credential libraries is updated
-- and a new session is created
-- only one of the wh_credential_dimensions is updated
begin;
select plan(4);
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials');
-- ensure no existing dimensions
select is(count(*), 0::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
-- insert session and session_credential_dynamic, should result in a three new credential dimensions
insert into session
( scope_id, target_id, host_set_id, host_id, user_id, auth_token_id, certificate, endpoint, public_id)
values
('p____bwidget', 't_________wb', 's___1wb-sths', 'h_____wb__01', 'u_____walter', 'tok___walter', 'abc'::bytea, 'ep1', 's1____walter');
insert into session_credential_dynamic
( session_id, library_id, credential_id, credential_purpose)
values
('s1____walter', 'vl______wvl1', null, 'application'),
('s1____walter', 'vl______wvl2', null, 'application'),
('s1____walter', 'vl______wvl3', null, 'application');
select is(count(*), 3::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
update credential_vault_library set vault_path = '/secrets/tcp/user' where public_id = 'vl______wvl2';
insert into session
( scope_id, target_id, host_set_id, host_id, user_id, auth_token_id, certificate, endpoint, public_id)
values
('p____bwidget', 't_________wb', 's___1wb-sths', 'h_____wb__01', 'u_____walter', 'tok___walter', 'abc'::bytea, 'ep1', 's2____walter');
insert into session_credential_dynamic
( session_id, library_id, credential_id, credential_purpose)
values
('s2____walter', 'vl______wvl1', null, 'application'),
('s2____walter', 'vl______wvl2', null, 'application'),
('s2____walter', 'vl______wvl3', null, 'application');
select is(count(*), 4::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
select is(count(*), 3::bigint) from wh_credential_dimension where organization_id = 'o_____widget' and current_row_indicator = 'Current';
select * from finish();
rollback;

@ -0,0 +1,51 @@
-- source tests the whx_credential_dimension_source view.
begin;
select plan(1);
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials');
insert into session
( scope_id, target_id, host_set_id, host_id, user_id, auth_token_id, certificate, endpoint, public_id)
values
('p____bwidget', 't_________wb', 's___1wb-sths', 'h_____wb__01', 'u_____walter', 'tok___walter', 'abc'::bytea, 'ep1', 's1____walter');
insert into session_credential_dynamic
( session_id, library_id, credential_id, credential_purpose)
values
('s1____walter', 'vl______wvl1', null, 'application');
select is(s.*, row(
's1____walter',
'application', -- credential_purpose,
'vl______wvl1', -- credential_library_id,
'vault credential library', -- credential_library_type,
'widget vault library', -- credential_library_name,
'None', -- credential_library_description,
'/secrets', -- credential_library_vault_path,
'GET', -- credential_library_vault_http_method,
'None', -- credential_library_vault_http_request_body,
'vs_______wvs', -- credential_store_id,
'vault credential store', -- credential_store_type,
'widget vault store', -- credential_store_name,
'None', -- credential_store_description,
'default', -- credential_store_vault_namespace,
'https://vault.widget', -- credential_store_vault_address,
't_________wb', -- target_id,
'tcp target', -- target_type,
'Big Widget Target', -- target_name,
'None', -- target_description,
0, -- target_default_port_number,
28800, -- target_session_max_seconds,
1, -- target_session_connection_limit,
'p____bwidget', -- project_id,
'Big Widget Factory', -- project_name,
'None', -- project_description,
'o_____widget', -- organization_id,
'Widget Inc', -- organization_name,
'None' -- organization_description
)::whx_credential_dimension_source)
from whx_credential_dimension_source as s
where s.target_id = 't_________wb';
rollback;

@ -0,0 +1,44 @@
-- target tests teh whx_credential_dimension_target view.
begin;
select plan(2);
select is_empty($$select * from whx_credential_dimension_target where target_id = 't_________wb'$$);
insert into wh_credential_dimension
(
key,
credential_purpose,
credential_library_id, credential_library_type, credential_library_name, credential_library_description, credential_library_vault_path, credential_library_vault_http_method, credential_library_vault_http_request_body,
credential_store_id, credential_store_type, credential_store_name, credential_store_description, credential_store_vault_namespace, credential_store_vault_address,
target_id, target_type, target_name, target_description, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id, project_name, project_description,
organization_id, organization_name, organization_description,
current_row_indicator, row_effective_time, row_expiration_time
)
values
(
'wcd________1',
'application',
'vl_______wvl', 'vault credential library', 'widget vault library', 'None', '/secrets', 'GET', 'None',
'vs_______wvs', 'vault credential store', 'widget vault store', 'None', 'blue', 'https://vault.widget',
't_________wb', 'tcp target', 'Big Widget Target', 'None', 0, 28800, 1,
'p____bwidget', 'Big Widget Factory', 'None',
'o_____widget', 'Widget Inc', 'None',
'Current', '2021-07-21T12:01'::timestamptz, 'infinity'::timestamptz
);
select is(t.*, row(
'wcd________1',
'application',
'vl_______wvl', 'vault credential library', 'widget vault library', 'None', '/secrets', 'GET', 'None',
'vs_______wvs', 'vault credential store', 'widget vault store', 'None', 'blue', 'https://vault.widget',
't_________wb', 'tcp target', 'Big Widget Target', 'None', 0, 28800, 1,
'p____bwidget', 'Big Widget Factory', 'None',
'o_____widget', 'Widget Inc', 'None'
)::whx_credential_dimension_target)
from whx_credential_dimension_target as t
where t.target_id = 't_________wb';
select * from finish();
rollback;

@ -0,0 +1,27 @@
--- insert tests a simple insert of session_credential_dynamic
begin;
select plan(6);
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials');
-- ensure no existing dimensions or bridge table rows
select is(count(*), 0::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
select is(count(*), 0::bigint) from wh_credential_group_membership where credential_group_key != 'no credentials' and credential_group_key != 'Unknown';
select is(count(*), 0::bigint) from wh_credential_group where key != 'no credentials' and key != 'Unknown';
--- insert single credential
insert into session
( scope_id, target_id, host_set_id, host_id, user_id, auth_token_id, certificate, endpoint, public_id)
values
('p____bwidget', 't_________wb', 's___1wb-sths', 'h_____wb__01', 'u_____walter', 'tok___walter', 'abc'::bytea, 'ep1', 's1____walter');
insert into session_credential_dynamic
( session_id, library_id, credential_id, credential_purpose)
values
('s1____walter', 'vl______wvl1', null, 'application');
select is(count(*), 1::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
select is(count(*), 1::bigint) from wh_credential_group_membership where credential_group_key != 'no credentials' and credential_group_key != 'Unknown';
select is(count(*), 1::bigint) from wh_credential_group where key != 'no credentials' and key != 'Unknown';
select * from finish();
rollback;

@ -0,0 +1,29 @@
--- insert_multiple tests inserting multiple session_credential_dynamic as a single statement
begin;
select plan(6);
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials');
-- ensure no existing dimensions
select is(count(*), 0::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
select is(count(*), 0::bigint) from wh_credential_group_membership where credential_group_key != 'no credentials' and credential_group_key != 'Unknown';
select is(count(*), 0::bigint) from wh_credential_group where key != 'no credentials' and key != 'Unknown';
--- multiple single credentials
insert into session
( scope_id, target_id, host_set_id, host_id, user_id, auth_token_id, certificate, endpoint, public_id)
values
('p____bwidget', 't_________wb', 's___1wb-sths', 'h_____wb__01', 'u_____walter', 'tok___walter', 'abc'::bytea, 'ep1', 's1____walter');
insert into session_credential_dynamic
( session_id, library_id, credential_id, credential_purpose)
values
('s1____walter', 'vl______wvl1', null, 'application'),
('s1____walter', 'vl______wvl2', null, 'application'),
('s1____walter', 'vl______wvl3', null, 'application'),
('s1____walter', 'vl______wvl3', null, 'egress');
select is(count(*), 4::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
select is(count(*), 4::bigint) from wh_credential_group_membership where credential_group_key != 'no credentials' and credential_group_key != 'Unknown';
select is(count(*), 1::bigint) from wh_credential_group where key != 'no credentials' and key != 'Unknown';
select * from finish();
rollback;

@ -1,5 +1,10 @@
package session
import (
"fmt"
"strings"
)
const (
activateStateCte = `
insert into session_state
@ -346,3 +351,34 @@ with
%s
`
)
const (
sessionCredentialDynamicBatchInsertBase = `
insert into session_credential_dynamic
( session_id, library_id, credential_purpose )
values
`
sessionCredentialDynamicBatchInsertValue = `
(?, ?, ?)`
sessionCredentialDynamicBatchInsertReturning = `
returning session_id, library_id, credential_id, credential_purpose
`
)
func batchInsertsessionCredentialDynamic(creds []*DynamicCredential) (string, []interface{}, error) {
if len(creds) <= 0 {
return "", nil, fmt.Errorf("empty slice of DynamicCredential, cannot build query")
}
batchInsertParams := make([]string, 0, len(creds))
batchInsertArgs := make([]interface{}, 0, len(creds)*3)
for _, cred := range creds {
batchInsertParams = append(batchInsertParams, sessionCredentialDynamicBatchInsertValue)
batchInsertArgs = append(batchInsertArgs, []interface{}{cred.SessionId, cred.LibraryId, cred.CredentialPurpose}...)
}
q := sessionCredentialDynamicBatchInsertBase + strings.Join(batchInsertParams, ",") + sessionCredentialDynamicBatchInsertReturning
return q, batchInsertArgs, nil
}

@ -89,14 +89,26 @@ func (r *Repository) CreateSession(ctx context.Context, sessionWrapper wrapping.
if err = w.Create(ctx, returnedSession); err != nil {
return errors.Wrap(ctx, err, op)
}
for _, cred := range newSession.DynamicCredentials {
cred.SessionId = newSession.PublicId
returnedCred := cred.clone()
if err = w.Create(ctx, returnedCred); err != nil {
}
// TODO: after upgrading to gorm v2 this batch insert can be replaced, since gorm v2 supports batch inserts
q, batchInsertArgs, err := batchInsertsessionCredentialDynamic(newSession.DynamicCredentials)
if err == nil {
rows, err := w.Query(ctx, q, batchInsertArgs)
if err != nil {
return errors.Wrap(ctx, err, op)
}
returnedSession.DynamicCredentials = append(returnedSession.DynamicCredentials, returnedCred)
defer rows.Close()
for rows.Next() {
var returnedCred DynamicCredential
w.ScanRows(rows, &returnedCred)
returnedSession.DynamicCredentials = append(returnedSession.DynamicCredentials, &returnedCred)
}
}
var foundStates []*State
// trigger will create new "Pending" state
if foundStates, err = fetchStates(ctx, read, returnedSession.PublicId); err != nil {

Loading…
Cancel
Save