diff --git a/db/build.gradle b/db/build.gradle index e5ade8b8b..07a18d33c 100644 --- a/db/build.gradle +++ b/db/build.gradle @@ -12,18 +12,45 @@ // See the License for the specific language governing permissions and // limitations under the License. +import org.gradle.api.internal.tasks.userinput.UserInputHandler + plugins { id "org.flywaydb.flyway" version "6.0.1" id 'maven-publish' } ext { + Set restrictedDbEnv = + [ 'sandbox', 'production' ].asUnmodifiable() + Set allDbEnv = + [ 'alpha', 'crash' ].plus(restrictedDbEnv).asUnmodifiable() + def dbServerProperty = 'dbServer' def dbNameProperty = 'dbName' - def dbServer = findProperty(dbServerProperty) + def dbServer = findProperty(dbServerProperty).toString().toLowerCase() def dbName = findProperty(dbNameProperty) + reconfirmRestrictedDbEnv = { + if (!restrictedDbEnv.contains(dbServer)) { + return + } + // For restricted environments, ask the user to type again to confirm. + // The following statement uses Gradle internal API to get around the + // missing console bug when Gradle Daemon is in use. Another option is + // to use the ant.input task. For details please refer to + // https://github.com/gradle/gradle/issues/1251. + def dbServerAgain = services.get(UserInputHandler.class).askQuestion( + """\ + Are you sure? Operating on ${dbServer} from desktop is unsafe. + Please type '${dbServer}' again to proceed: """.stripIndent(), + '').trim() + if (dbServer != dbServerAgain) { + throw new RuntimeException( + "Failed to confirm for restricted database environment. Operation aborted.") + } + } + getAccessInfoByHostPort = { hostAndPort -> return [ url: "jdbc:postgresql://${hostAndPort}/${dbName}", @@ -31,8 +58,8 @@ ext { password: findProperty('dbPassword')] } - getSocketFactoryAccessInfo = { - def cred = getCloudSqlCredential('alpha', 'superuser').split(' ') + getSocketFactoryAccessInfo = { env -> + def cred = getCloudSqlCredential(env, 'admin').split(' ') def sqlInstance = cred[0] return [ url: """\ @@ -46,11 +73,10 @@ ext { } getJdbcAccessInfo = { - switch (dbServer.toString().toLowerCase()) { - case 'alpha': - return getSocketFactoryAccessInfo() - default: - return getAccessInfoByHostPort(dbServer) + if (allDbEnv.contains(dbServer)) { + return getSocketFactoryAccessInfo(dbServer) + } else { + return getAccessInfoByHostPort(dbServer) } } @@ -62,13 +88,16 @@ ext { // later). getCloudSqlCredential = { env, role -> env = env == 'production' ? '' : "-${env}" + def keyProject = env == '-crash' + ? 'domain-registry-crash-kms-keys' + : "domain-registry${env}-keys" def command = """gsutil cp \ - gs://domain-registry${env}-cloudsql-credentials/${role}.enc - | \ + gs://domain-registry${env}-cloudsql-credentials/${role}_credential.enc - | \ gcloud kms decrypt --location global --keyring nomulus \ --key sql-credentials-on-gcs-key --plaintext-file=- \ --ciphertext-file=- \ - --project=domain-registry${env}-keys""" + --project=${keyProject}""" return execInBash(command, '/tmp') } @@ -114,6 +143,13 @@ flyway { locations = [ "classpath:sql/flyway" ] } +tasks.flywayMigrate.dependsOn( + tasks.create('confirmMigrateOnRestrictedDb') { + doLast { + project.ext.reconfirmRestrictedDbEnv() + } + }) + dependencies { def deps = rootProject.dependencyMap diff --git a/db/src/main/resources/sql/user/initialize_roles.sql b/db/src/main/resources/sql/user/initialize_roles.sql index 0c5b9c675..eaaa09c74 100644 --- a/db/src/main/resources/sql/user/initialize_roles.sql +++ b/db/src/main/resources/sql/user/initialize_roles.sql @@ -16,7 +16,7 @@ -- This script should run once under the **'postgres'** user before any other -- roles or users are created. -# Prevent backdoor grants through the implicit 'public' role. +-- Prevent backdoor grants through the implicit 'public' role. REVOKE ALL PRIVILEGES ON SCHEMA public from public; CREATE ROLE readonly;