Skip to content

Commit

Permalink
Support revocation of tenant keys #2211
Browse files Browse the repository at this point in the history
Signed-off-by: Paul Bastide <pbastide@us.ibm.com>
  • Loading branch information
prb112 committed Jun 7, 2021
1 parent 6427950 commit 88319aa
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* (C) Copyright IBM Corp. 2021
*
* SPDX-License-Identifier: Apache-2.0
*/

package com.ibm.fhir.database.utils.tenant;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import com.ibm.fhir.database.utils.api.IDatabaseStatement;
import com.ibm.fhir.database.utils.api.IDatabaseTranslator;
import com.ibm.fhir.database.utils.common.DataDefinitionUtil;

/**
* DAO to delete a tenant key record
*/
public class DeleteTenantKeyDAO implements IDatabaseStatement {
private final String schemaName;
private final int tenantId;
private final String tenantKey;
private int count = 0;

/**
* Public constructor
*
* @param schemaName
* @param tenantId
* @param tenantKey
*/
public DeleteTenantKeyDAO(String schemaName, int tenantId, String tenantKey) {
DataDefinitionUtil.assertValidName(schemaName);
this.schemaName = schemaName;
this.tenantId = tenantId;
this.tenantKey = tenantKey;
}

@Override
public void run(IDatabaseTranslator translator, Connection c) {
final String tableName = DataDefinitionUtil.getQualifiedName(schemaName, "TENANT_KEYS");
final String SQL;
if (tenantKey == null) {
SQL = "DELETE FROM " + tableName + " WHERE mt_id = ?";
} else {
SQL = "DELETE FROM " + tableName + " WHERE mt_id = ?"
+ " AND tenant_hash = sysibm.hash(tenant_salt || ?, 2)";
}

try (PreparedStatement ps = c.prepareStatement(SQL)) {
ps.setInt(1, tenantId);

if (tenantKey != null) {
ps.setString(2, tenantKey);
}
count = ps.executeUpdate();
} catch (SQLException x) {
// Translate the exception into something a little more meaningful
// for this database type and application
throw translator.translate(x);
}
}

/**
* @return total number of keys removed
*/
public int getCount() {
return count;
}
}
43 changes: 43 additions & 0 deletions fhir-persistence-schema/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ Once the server has determined the tenant id for a given request, it uses this t
used in conjunction to create or retrieve data for this tenant.
For more information on multi-tenancy, see section [4.9 Multi-tenancy of the IBM FHIR Server Users Guide](https://ibm.github.io/FHIR/guides/FHIRServerUsersGuide#49-multi-tenancy).


### Refresh Tenant Following Schema Update (Db2 only)

After a schema update you must run the refresh-tenants command to ensure that any new tables added by the update have the correct partitions. The refresh-tenants process will iterate over each tenant and allocate new partitions as needed. This step is idempotent, so you can run it more than once if required.
Expand Down Expand Up @@ -212,6 +213,48 @@ Note, you may want to add a tenant key when a key is lost or needs to be changed

Use `--tenant-key-file tenant.key.file` to direct the action to read the tenant-key from file. If the file exists the tenant key (up to 44 characters is read from the file. If the file does not exist, the generated tenantKey is written out to the file.


### Remove all tenant keys from an Existing Tenant (Db2 only)
To remove all tenant keys for an existing tenant, replace FHIRDATA with your client schema, and change default to your tenant's name.

```
--prop-file db2.properties
--schema-name FHIRDATA
--db-type db2
--revoke-all-tenant-keys default
```

**Example Output**
```
2021-06-07 15:30:41.782 00000001 INFO .common.JdbcConnectionProvider Opening connection to database: jdbc:db2://demodb2:50000/fhirdb
2021-06-07 15:30:42.405 00000001 INFO com.ibm.fhir.schema.app.Main Tenant Key revoked for 'default' total removed=[1]
2021-06-07 15:30:42.419 00000001 INFO com.ibm.fhir.schema.app.Main Processing took: 0.699 s
2021-06-07 15:30:42.420 00000001 INFO com.ibm.fhir.schema.app.Main SCHEMA CHANGE: OK
```

Use `--tenant-key-file tenant.key.file` to direct the action to read the tenant-key from file. If the file exists the tenant key (up to 44 characters is read from the file. If the file does not exist, the generated tenantKey is written out to the file.

### Remove a tenant key key from an Existing Tenant (Db2 only)
To remove a tenant key for an existing tenant, replace FHIRDATA with your client schema, and change default to your tenant's name.

```
--prop-file db2.properties
--schema-name FHIRDATA
--db-type db2
--revoke-tenant-key default
--tenant-key rZ59TLyEpjU+FAKEtgVk8J44J0=
```

**Example Output**
```
2021-06-07 15:30:41.782 00000001 INFO .common.JdbcConnectionProvider Opening connection to database: jdbc:db2://demodb2:50000/fhirdb
2021-06-07 15:30:42.405 00000001 INFO com.ibm.fhir.schema.app.Main Tenant Key revoked for 'default' total removed=[1]
2021-06-07 15:30:42.419 00000001 INFO com.ibm.fhir.schema.app.Main Processing took: 0.699 s
2021-06-07 15:30:42.420 00000001 INFO com.ibm.fhir.schema.app.Main SCHEMA CHANGE: OK
```

Use `--tenant-key-file tenant.key.file` to direct the action to read the tenant-key from file. If the file exists the tenant key (up to 44 characters is read from the file. If the file does not exist, the generated tenantKey is written out to the file.

### Update the stored procedures and functions for FHIRDATA (and not FHIR_ADMIN) (Db2 and PostgreSQL)

For Db2:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import com.ibm.fhir.database.utils.pool.PoolConnectionProvider;
import com.ibm.fhir.database.utils.postgres.PostgresTranslator;
import com.ibm.fhir.database.utils.tenant.AddTenantKeyDAO;
import com.ibm.fhir.database.utils.tenant.DeleteTenantKeyDAO;
import com.ibm.fhir.database.utils.tenant.GetTenantDAO;
import com.ibm.fhir.database.utils.transaction.SimpleTransactionProvider;
import com.ibm.fhir.database.utils.transaction.TransactionFactory;
Expand Down Expand Up @@ -170,6 +171,7 @@ public class Main {
private boolean listTenants;
private boolean dropDetached;
private boolean deleteTenantMeta;
private boolean revokeTenantKey;

// Tenant Key Output or Input File
private String tenantKeyFileName;
Expand Down Expand Up @@ -1237,7 +1239,40 @@ protected void deleteTenantMeta() {
throw x;
}
}
}

/**
* revokes a tenant key or if no tenant key is specified remove all of them for the
* given tenant.
*/
protected void revokeTenantKey() {
if (!MULTITENANT_FEATURE_ENABLED.contains(dbType)) {
return;
}

TenantInfo tenantInfo = getTenantInfo();
int tenantId = tenantInfo.getTenantId();

// Only if the Tenant Key file is provided as a parameter is it not null.
// in this case we want special behavior.
if (tenantKeyFileUtil.keyFileExists(tenantKeyFileName)) {
tenantKey = this.tenantKeyFileUtil.readTenantFile(tenantKeyFileName);
}

try (ITransaction tx = TransactionFactory.openTransaction(connectionPool)) {
try {
DeleteTenantKeyDAO dtk = new DeleteTenantKeyDAO(schema.getAdminSchemaName(), tenantId, tenantKey);

Db2Adapter adapter = new Db2Adapter(connectionPool);
adapter.runStatement(dtk);
int count = dtk.getCount();
logger.info("Tenant Key revoked for '" + tenantInfo.getTenantName() + "' total removed=[" + count + "]");
} catch (DataAccessException x) {
// Something went wrong, so mark the transaction as failed
tx.setRollbackOnly();
throw x;
}
}
}

//-----------------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -1327,6 +1362,20 @@ protected void parseArgs(String[] args) {
throw new IllegalArgumentException("Missing value for argument at posn: " + i);
}
break;
case "--revoke-tenant-key":
if (++i >= args.length) {
throw new IllegalArgumentException("Missing value for argument at posn: " + i);
}
this.tenantName = args[i];
this.revokeTenantKey = true;
break;
case "--revoke-all-tenant-keys":
if (++i >= args.length) {
throw new IllegalArgumentException("Missing value for argument at posn: " + i);
}
this.tenantName = args[i];
this.revokeTenantKey = true;
break;
case "--update-proc":
this.updateProc = true;
break;
Expand Down Expand Up @@ -1824,6 +1873,8 @@ protected void process() {
deleteTenantMeta();
} else if (this.dropTenant) {
dropTenant();
} else if (this.revokeTenantKey) {
revokeTenantKey();
}

if (this.grantTo != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ public static void printUsage() {
ps.println("--list-tenants");
ps.println(" * fetches list of tenants and current status");

ps.println("--revoke-all-tenant-keys");
ps.println(" * revokes the all of the keys for the specified tenant");

ps.println("--revoke-tenant-key");
ps.println(" * revokes the key for the specified tenant and tenant key");

// Dry Run functionality
ps.println("--dry-run ");
ps.println(" * simulates the actions of the actions that change the datastore");
Expand Down

0 comments on commit 88319aa

Please sign in to comment.