Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CB-3834 Secret manager database schema #2327

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public abstract class BaseWebAppConfiguration implements WebAppConfiguration {

protected final Map<String, Object> plugins;
protected String defaultUserTeam;
protected String defaultUserCredentialsProfile;
protected boolean resourceManagerEnabled;
protected boolean showReadOnlyConnectionInfo;
protected String[] enabledFeatures;
Expand Down Expand Up @@ -105,4 +106,9 @@ public void setEnabledFeatures(String[] enabledFeatures) {
public boolean isShowReadOnlyConnectionInfo() {
return showReadOnlyConnectionInfo;
}


public String getDefaultUserCredentialsProfile() {
return defaultUserCredentialsProfile;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package io.cloudbeaver.model.app;

import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;

import java.util.Map;

Expand All @@ -32,6 +33,9 @@ public interface WebAppConfiguration {

String getDefaultUserTeam();

@Nullable
String getDefaultUserCredentialsProfile();

<T> T getPluginOption(@NotNull String pluginId, @NotNull String option);

Map<String, Object> getPluginConfig(@NotNull String pluginId, boolean create);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,9 @@ public String toString() {
public String getAuthRole() {
return user.getAuthRole();
}

public String getCredentialsProfileId() {
return user.getCredentialsProfileId();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type AdminUserInfo {
linkedAuthProviders: [String!]!
enabled: Boolean!
authRole: String
credentialsProfileId: String @since(version: "23.3.4")
}

type AdminTeamInfo {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ public String getAuthRole() {
return user.getAuthRole();
}

@Property
public String getCredentialsProfileId() {
return user.getCredentialsProfileId();
}


@Property
public Map<String, String> getMetaParameters() {
return user.getMetaParameters();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,30 @@ CREATE TABLE {table_prefix}CB_OBJECT_PERMISSIONS
FOREIGN KEY (SUBJECT_ID) REFERENCES {table_prefix}CB_AUTH_SUBJECT (SUBJECT_ID) ON DELETE CASCADE
);

CREATE TABLE {table_prefix}CB_CREDENTIALS_PROFILE
(
PROFILE_ID VARCHAR(128) NOT NULL,
PROFILE_NAME VARCHAR(100) NOT NULL,
PROFILE_DESCRIPTION VARCHAR(255) NOT NULL,
PARENT_PROFILE_ID VARCHAR(128) NULL,
CREATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,

PRIMARY KEY (PROFILE_ID),
FOREIGN KEY (PROFILE_ID) REFERENCES {table_prefix}CB_AUTH_SUBJECT (SUBJECT_ID) ON DELETE CASCADE,
FOREIGN KEY (PARENT_PROFILE_ID) REFERENCES {table_prefix}CB_CREDENTIALS_PROFILE(PROFILE_ID) ON DELETE NO ACTION
);

CREATE TABLE {table_prefix}CB_USER
(
USER_ID VARCHAR(128) NOT NULL,

IS_ACTIVE CHAR(1) NOT NULL,
CREATE_TIME TIMESTAMP NOT NULL,
DEFAULT_AUTH_ROLE VARCHAR(32) NULL,
CREDENTIALS_PROFILE_ID VARCHAR(128) NULL,

PRIMARY KEY (USER_ID),
FOREIGN KEY (USER_ID) REFERENCES {table_prefix}CB_AUTH_SUBJECT (SUBJECT_ID) ON DELETE CASCADE
FOREIGN KEY (USER_ID) REFERENCES {table_prefix}CB_AUTH_SUBJECT (SUBJECT_ID) ON DELETE CASCADE,
FOREIGN KEY (CREDENTIALS_PROFILE_ID) REFERENCES {table_prefix}CB_CREDENTIALS_PROFILE(PROFILE_ID) ON DELETE NO ACTION
);

-- Additional user properties (profile)
Expand Down Expand Up @@ -315,3 +329,25 @@ CREATE TABLE {table_prefix}CB_USER_SECRETS
PRIMARY KEY (USER_ID, SECRET_ID),
FOREIGN KEY (USER_ID) REFERENCES {table_prefix}CB_USER (USER_ID) ON DELETE CASCADE
);

CREATE TABLE {table_prefix}CB_SUBJECT_SECRETS
(
SUBJECT_ID VARCHAR(128) NOT NULL,
SECRET_ID VARCHAR(255) NOT NULL,

PROJECT_ID VARCHAR(128),
OBJECT_TYPE VARCHAR(32),
OBJECT_ID VARCHAR(128),

SECRET_VALUE TEXT NOT NULL,

ENCODING_TYPE VARCHAR(32) DEFAULT 'PLAINTEXT' NOT NULL,
CREATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
UPDATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,

PRIMARY KEY (SUBJECT_ID, SECRET_ID),
FOREIGN KEY (SUBJECT_ID) REFERENCES {table_prefix}CB_AUTH_SUBJECT (SUBJECT_ID) ON DELETE CASCADE
);

CREATE INDEX IDX_SUBJECT_SECRETS_PROJECT ON {table_prefix}CB_SUBJECT_SECRETS (PROJECT_ID,SUBJECT_ID);
CREATE INDEX IDX_SUBJECT_SECRETS_OBJECT ON {table_prefix}CB_SUBJECT_SECRETS (PROJECT_ID,OBJECT_TYPE,OBJECT_ID);
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
CREATE TABLE {table_prefix}CB_SUBJECT_SECRETS
(
SUBJECT_ID VARCHAR(128) NOT NULL,
SECRET_ID VARCHAR(255) NOT NULL,

PROJECT_ID VARCHAR(128),
OBJECT_TYPE VARCHAR(32),
OBJECT_ID VARCHAR(128),

SECRET_VALUE TEXT NOT NULL,

ENCODING_TYPE VARCHAR(32) DEFAULT 'PLAINTEXT' NOT NULL,
CREATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
UPDATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,

PRIMARY KEY (SUBJECT_ID, SECRET_ID),
FOREIGN KEY (SUBJECT_ID) REFERENCES {table_prefix}CB_AUTH_SUBJECT (SUBJECT_ID) ON DELETE CASCADE
);

CREATE INDEX IDX_SUBJECT_SECRETS_PROJECT ON {table_prefix}CB_SUBJECT_SECRETS (PROJECT_ID,SUBJECT_ID);
CREATE INDEX IDX_SUBJECT_SECRETS_OBJECT ON {table_prefix}CB_SUBJECT_SECRETS (PROJECT_ID,OBJECT_TYPE,OBJECT_ID);

INSERT INTO {table_prefix}CB_SUBJECT_SECRETS (SUBJECT_ID, SECRET_ID, SECRET_VALUE, ENCODING_TYPE, CREATE_TIME, UPDATE_TIME)
SELECT USER_ID, SECRET_ID, SECRET_VALUE, ENCODING_TYPE, UPDATE_TIME, UPDATE_TIME FROM {table_prefix}CB_USER_SECRETS;

DROP TABLE {table_prefix}CB_USER_SECRETS;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
CREATE TABLE {table_prefix}CB_CREDENTIALS_PROFILE
(
PROFILE_ID VARCHAR(128) NOT NULL,
PROFILE_NAME VARCHAR(100) NOT NULL,
PROFILE_DESCRIPTION VARCHAR(255) NOT NULL,
PARENT_PROFILE_ID VARCHAR(255) NULL,

CREATE_TIME TIMESTAMP NOT NULL,

PRIMARY KEY (PROFILE_ID),
FOREIGN KEY (PROFILE_ID) REFERENCES {table_prefix}CB_AUTH_SUBJECT (SUBJECT_ID) ON DELETE CASCADE,
FOREIGN KEY (PARENT_PROFILE_ID) REFERENCES {table_prefix}CB_CREDENTIALS_PROFILE(PROFILE_ID) ON DELETE NO ACTION
);

ALTER TABLE {table_prefix}CB_USER ADD COLUMN CREDENTIALS_PROFILE_ID VARCHAR(128) NULL;
ALTER TABLE {table_prefix}CB_USER ADD FOREIGN KEY(CREDENTIALS_PROFILE_ID) REFERENCES {table_prefix}CB_CREDENTIALS_PROFILE(PROFILE_ID) ON DELETE NO ACTION;
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>

private static final String SUBJECT_USER = "U";
private static final String SUBJECT_TEAM = "R";
private static final String SUBJECT_PROFILE = "P";
private static final Type MAP_STRING_OBJECT_TYPE = new TypeToken<Map<String, Object>>() {
}.getType();
private static final Gson gson = new GsonBuilder().create();
Expand Down Expand Up @@ -300,15 +301,18 @@ public SMUser getUserById(String userId) throws DBException {
try (Connection dbCon = database.openConnection()) {
SMUser user;
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames("SELECT USER_ID,IS_ACTIVE,DEFAULT_AUTH_ROLE FROM {table_prefix}CB_USER WHERE USER_ID=?")
database.normalizeTableNames(
"SELECT USER_ID,IS_ACTIVE,DEFAULT_AUTH_ROLE,CREDENTIALS_PROFILE_ID FROM {table_prefix}CB_USER " +
"WHERE USER_ID=?")
)) {
dbStat.setString(1, userId);
try (ResultSet dbResult = dbStat.executeQuery()) {
if (dbResult.next()) {
String userName = dbResult.getString(1);
String active = dbResult.getString(2);
String authRole = dbResult.getString(3);
user = new SMUser(userName, CHAR_BOOL_TRUE.equals(active), authRole);
String credentialsProfileId = dbResult.getString(4);
user = new SMUser(userName, CHAR_BOOL_TRUE.equals(active), authRole, credentialsProfileId);
} else {
return null;
}
Expand Down Expand Up @@ -373,7 +377,8 @@ public SMUser[] findUsers(@NotNull SMUserFilter filter)
Map<String, SMUser> result = new LinkedHashMap<>();
// Read users
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames("SELECT USER_ID,IS_ACTIVE,DEFAULT_AUTH_ROLE FROM {table_prefix}CB_USER"
database.normalizeTableNames("SELECT USER_ID,IS_ACTIVE,DEFAULT_AUTH_ROLE,CREDENTIALS_PROFILE_ID FROM " +
"{table_prefix}CB_USER"
+ buildUsersFilter(filter) + "\nORDER BY USER_ID " + getOffsetLimitPart(filter)))) {
int parameterIndex = setUsersFilterValues(dbStat, filter, 1);

Expand All @@ -382,7 +387,9 @@ public SMUser[] findUsers(@NotNull SMUserFilter filter)
String userId = dbResult.getString(1);
String active = dbResult.getString(2);
String authRole = dbResult.getString(3);
result.put(userId, new SMUser(userId, CHAR_BOOL_TRUE.equals(active), authRole));
String credentialsProfileId = dbResult.getString(4);
result.put(userId,
new SMUser(userId, CHAR_BOOL_TRUE.equals(active), authRole, credentialsProfileId));
}
}
}
Expand Down Expand Up @@ -971,7 +978,10 @@ private SMTeam fetchTeam(ResultSet dbResult) throws SQLException {
}

@Override
public void createTeam(String teamId, String name, String description, String grantor) throws DBCException {
public void createTeam(
@NotNull String teamId, @Nullable String name, @Nullable String description,
@NotNull String grantor
) throws DBCException {
if (CommonUtils.isEmpty(teamId)) {
throw new DBCException("Empty team name is not allowed");
}
Expand Down Expand Up @@ -1000,7 +1010,7 @@ public void createTeam(String teamId, String name, String description, String gr
txn.commit();
}
} catch (SQLException e) {
throw new DBCException("Error saving tem in database", e);
throw new DBCException("Error saving team in database", e);
}
}

Expand Down Expand Up @@ -1078,6 +1088,104 @@ public void deleteTeam(String teamId, boolean force) throws DBCException {
}
}

@Override
@NotNull
public SMCredentialsProfile[] readAllCredentialsProfiles() throws DBCException {
List<SMCredentialsProfile> profiles = new ArrayList<>();
try (var dbCon = database.openConnection();
var dbStat = dbCon.prepareStatement(database.normalizeTableNames(
"SELECT PROFILE_ID, PROFILE_NAME, PROFILE_DESCRIPTION, PARENT_PROFILE_ID FROM " +
"{table_prefix}CB_CREDENTIALS_PROFILE ORDER BY PROFILE_ID"
));
var dbResult = dbStat.executeQuery();
) {
while (dbResult.next()) {
String profileId = dbResult.getString(1);
String name = dbResult.getString(2);
String description = dbResult.getString(3);
String parentProfile = dbResult.getString(4);
profiles.add(new SMCredentialsProfile(profileId, name, description, parentProfile));
}
return profiles.toArray(SMCredentialsProfile[]::new);
} catch (SQLException e) {
throw new DBCException("Error reading credentials profiles from database", e);
}
}

@Override
public void createCredentialsProfile(@NotNull SMCredentialsProfile credentialsProfile) throws DBCException {
if (isSubjectExists(credentialsProfile.getCredentialsProfileId())) {
throw new DBCException("User, team or credentials profile '" + credentialsProfile.getCredentialsProfileId() + "' already " +
"exists");
}

try (var dbCon = database.openConnection();
var txn = new JDBCTransaction(dbCon)) {
createAuthSubject(dbCon, credentialsProfile.getCredentialsProfileId(), SUBJECT_TEAM);
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames("INSERT INTO {table_prefix}CB_CREDENTIALS_PROFILE" +
"(PROFILE_ID,PROFILE_NAME,PROFILE_DESCRIPTION,PARENT_PROFILE_ID) VALUES(?,?,?,?)"))) {
dbStat.setString(1, credentialsProfile.getCredentialsProfileId());
dbStat.setString(2, CommonUtils.notEmpty(credentialsProfile.getName()));
dbStat.setString(3, CommonUtils.notEmpty(credentialsProfile.getProfileDescription()));
if (CommonUtils.isNotEmpty(credentialsProfile.getParentProfileId())) {
dbStat.setString(4, credentialsProfile.getParentProfileId());
} else {
dbStat.setNull(4, Types.VARCHAR);
}
dbStat.execute();
}
txn.commit();
} catch (SQLException e) {
throw new DBCException("Error saving credentials profile in database", e);
}
}

@Override
public void deleteCredentialsProfile(@NotNull String credentialsProfileId) throws DBCException {
String defaultUserCredentialsProfile = application.getAppConfiguration().getDefaultUserCredentialsProfile();
if (CommonUtils.isNotEmpty(defaultUserCredentialsProfile)
&& defaultUserCredentialsProfile.equals(credentialsProfileId)) {
throw new DBCException("Default credentials profile cannot be deleted");
}

try (var dbCon = database.openConnection();
var txn = new JDBCTransaction(dbCon)) {
deleteAuthSubject(dbCon, credentialsProfileId);
JDBCUtils.executeStatement(
dbCon,
database.normalizeTableNames("DELETE FROM {table_prefix}CB_CREDENTIALS_PROFILE WHERE PROFILE_ID=?"),
credentialsProfileId
);
txn.commit();
} catch (SQLException e) {
throw new DBCException("Error deleting credentials profile from database", e);
}
}

@Override
public void setUserCredentialsProfile(@NotNull String userId, @Nullable String credentialsProfileId)
throws DBCException {
try (var dbCon = database.openConnection();
PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames(
"UPDATE {table_prefix}CB_USER SET CREDENTIALS_PROFILE_ID=? WHERE USER_ID=?"))
) {
if (CommonUtils.isEmpty(credentialsProfileId)) {
String defaultCredentialsProfile = application.getAppConfiguration().getDefaultUserCredentialsProfile();
if (defaultCredentialsProfile == null) {
dbStat.setNull(1, Types.VARCHAR);
} else {
dbStat.setString(1, defaultCredentialsProfile);
}
} else {
dbStat.setString(1, credentialsProfileId);
}
} catch (SQLException e) {
throw new DBCException("Error saving credentials profile for user", e);
}
}

///////////////////////////////////////////
// Subject functions

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public class CBDatabase {
public static final String SCHEMA_UPDATE_SQL_PATH = "db/cb_schema_update_";

private static final int LEGACY_SCHEMA_VERSION = 1;
private static final int CURRENT_SCHEMA_VERSION = 15;
private static final int CURRENT_SCHEMA_VERSION = 17;

private static final String DEFAULT_DB_USER_NAME = "cb-data";
private static final String DEFAULT_DB_PWD_FILE = ".database-credentials.dat";
Expand Down Expand Up @@ -301,7 +301,7 @@ private SMUser createAdminUser(
SMUser adminUser = adminSecurityController.getUserById(adminName);

if (adminUser == null) {
adminUser = new SMUser(adminName, true, "ADMINISTRATOR");
adminUser = new SMUser(adminName, true, "ADMINISTRATOR", null);
adminSecurityController.createUser(adminUser.getUserId(), adminUser.getMetaParameters(), true, adminUser.getAuthRole());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package io.cloudbeaver.service.security.db;

import org.jkiss.dbeaver.model.security.user.SMCredentialsProfile;
import org.jkiss.dbeaver.model.security.user.SMTeam;

import java.util.List;
Expand All @@ -24,6 +25,7 @@ class CBDatabaseInitialData {
private String adminName;
private String adminPassword;
private List<SMTeam> teams;
private List<SMCredentialsProfile> credentialsProfiles;

public String getAdminName() {
return adminName;
Expand All @@ -37,6 +39,10 @@ public List<SMTeam> getTeams() {
return teams;
}

public List<SMCredentialsProfile> getCredentialsProfiles() {
return credentialsProfiles;
}

public void setTeams(List<SMTeam> teams) {
this.teams = teams;
}
Expand Down
Loading