Merge remote-tracking branch 'origin/main' into plugin-hostcatalogs

This commit is contained in:
Jeff Mitchell committed 2021-09-16 14:42:52 -04:00
commit a71cdae204
42 files changed
+2333 -323

No files matched your search

@@ -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
}