diff --git a/README.md b/README.md index 1b567c0..7f99694 100644 --- a/README.md +++ b/README.md @@ -3,16 +3,15 @@ A Go broker for MSSQL Service ## Summary -The cf-mssql-broker project implements and exposes the CF (Cloud Foundy) Service Broker API (http://docs.cloudfoundry.org/services/api.html) to facilitate the managemnt of a single Microsoft SQL Server instance. The minimum version supported is SQL Server 2012 Express. +The cf-mssql-broker project implements and exposes the CF (Cloud Foundy) [Service Broker API](http://docs.cloudfoundry.org/services/api.html) to facilitate the managemnt of a single Microsoft SQL Server instance. The minimum version supported is SQL Server 2012 Express. -For the provision operation of a CF service instance the broker will create a contained database (https://msdn.microsoft.com/en-us/library/ff929071.aspx) on the targeted SQL Server. +For the provision operation of a CF service instance the broker will create a [contained database](https://msdn.microsoft.com/en-us/library/ff929071.aspx) on the targeted SQL Server. For the binding operation the broker will create an SQL Server user and a ramdomly genrated password in the contained database of the CF service instance. The broker serive does not need to have save any state, thus it can be farmed or deployed on another box without any data migration. To keep track of provisioned instances and bindings it will use the IDs from Service Broker API in the database name and in the SQL Server user name. - ## SQL Server config ### Enable TCP access for SQL Server @@ -23,15 +22,18 @@ Use the sql management studio to enable tcp or use the following doc to automate To configure or automate the firewall for SQL Server use the following PS example: +```sh New-NetFirewallRule -DisplayName “SQL Server” -Direction Inbound –Protocol TCP –LocalPort 1433 -Action allow +``` ### Enable Contained Database Authentication -Binding operations will create a user with a password only in the contained database. This is disabled by -default in SQL Server 2012 and 2014. Use the following commmand to enable contained database authentication: +Binding operations will create a user with a password only in the contained database. This is disabled by default in SQL Server 2012 and 2014. Use the following commmand to enable contained database authentication: +```sh SQLCmd -S .\sqlexpress -Q "EXEC sp_configure 'contained database authentication', 1; reconfigure;" +``` ### Tips and Tricks @@ -40,16 +42,15 @@ choco install mssqlserver2012express ## Configuration -cf_mssql_broker_config.json is the default configuration file. The config file can be overridden with -the following flag: -config=/new/path/config.json +cf_mssql_broker_config.json is the default configuration file. The config file can be overridden with the following flag: -config=/new/path/config.json + +The `servedMssqlBindingHostname` and `servedMssqlBindingPort` properties need to be changed for every installetion. They are the hostname and port that are sent to the CF applications, and need to be accessible from the CF application network. NOTE: Do not change this value on a existing mssql broker with active bindings. If this is necessary, extra migrations steps need to be taken for the exsing bindings in the CF's Cloud Controller. -The servedMssqlBindingHostname and servedMssqlBindingPort need to be changed for every installetion. -They are the hostname and port that are sent to the CF applicatoins, and need to be accessible from the CF application network. NOTE: Do not change this value on a existing mssql broker with active bindings. -It this is necessary, extra migrations steps need to be taken for the exsing bindings in the CF's Cloud Controller. +`logLevel` will set the logging level. Accepted levels: "debug", "info", "error", and "fatal". -The brokerGoSqlDriver and brokerMssqlConnection are settings that the broker uses to connect to the mssql instance. brokerGoSqlDriver can be odbc (recommanded https://code.google.com/p/odbc/) for mssql (experimental https://github.com/denisenkom/go-mssqldb). brokerMssqlConnection is a map that is -converted into a connection string (e.g. "server=localhost;port=1433") consumed by odbc or mssql go library. -Exmaple for a local trusted brokerMssqlConnection with odbc driver: +The `brokerGoSqlDriver` and `brokerMssqlConnection` are settings that the broker uses to connect to the mssql instance. `brokerGoSqlDriver` can be "odbc"" (recommanded https://code.google.com/p/odbc/) or "mssql" (experimental https://github.com/denisenkom/go-mssqldb). `brokerMssqlConnection` is a key-value json object that is +converted into a connection string (e.g. {"server":"localhost","port":1433} is converted to "server=localhost;port=1433") consumed by odbc or mssql go library. +Exmaple for a local trusted `brokerMssqlConnection` with odbc driver: { "server": "localhost\\sqlexpress", "database": "master", @@ -57,16 +58,22 @@ Exmaple for a local trusted brokerMssqlConnection with odbc driver: "trusted_connection": "yes" } -listeningAddr and brokerCredentials are used for the brokers http server. The CF CloudController will use this setting to connect to the broker. +`listeningAddr` and `brokerCredentials` are used for the brokers http server. The CF CloudController will use this setting to connect to the broker. -dbIdentifierPrefix appended at the begining of the instance ID for the SQL Server database name, and it is appended at the begining of the binding id for the SQL Server user name. This will allow an adim to easily idetify the databases managed by a particular mssql broker. +`dbIdentifierPrefix` is a string that is appended at the begining of the instance ID for the SQL Server database name, and at the begining of the binding id for the SQL Server user name. This will allow operators to easily idetify the databases managed by a particular mssql broker. Do not change this value on a existing mssql broker with active instances. -serviceCatalog JSON is sent to the Cloud Controller to identify the service name and plans, and provide additional description to the user. +`serviceCatalog` is a JSON object using the CF Service API catalog format and is sent to the Cloud Controller to identify the service name and plans, and provide a description to the user about the service. To add more mssql brokers to the same CF cluster will require the following changes: + > unique "name" for the service + > unique "id" for the service + > unique "id" for the plan + +`grantDbOwnerForBindings` sets the role for the binding credentails. If it is `true` the binding credential will have db_owner role. If it is `false` it wil have the db_ddladmin role and all grants for the dbo schema in the instnace database. It is not recommanded to change this value on a existing mssql broker with active bindings. Warning: setting tis value to `true` is dagerous and will allow the users to create backups (i.e. leak disk resources), drop the database (broker needs extra error and orphan handling), drop/create other users (i.e. delete cf bindings). ## Building and running Setup you GOPATH env variable +```sh go get github.com/tools/godep go get github.com/hpcloud/cf-mssql-broker @@ -74,31 +81,42 @@ cd $GOPATH/src/github.com/hpcloud/cf-mssql-broker # cd $env:GOPATH/src/github.co godep restore go build -cf-mssql-broker -config=cf_mssql_broker_config.json +# change the required values from the reference config file (cf_mssql_broker_config.json) +cf-mssql-broker -config=cf_mssql_broker_config.json +``` ## Using the broker with Curl REST calls ### Provision Instance +```sh curl http://username:password@localhost:3000/v2/service_instances/instance1 -d '{ "service_id": "b6844738-382b-4a9e-9f80-2ff5049d512f", "plan_id": "fb740fd7-2029-467a-9256-63ecd882f11c", "organization_guid": "org-guid-here", "space_guid": "space-guid-here" }' -X PUT -H "X-Broker-API-Version: 2.4" -H "Content-Type: application/json" +``` ### Bind Service Instance +```sh curl http://username:password@localhost:3000/v2/service_instances/instance1/service_bindings/binding1 -d '{ "plan_id": "plan-guid-here", "service_id": "service-guid-here", "app_guid": "app-guid-here"}' -X PUT -H "X-Broker-API-Version: 2.4" -H "Content-Type: application/json" +``` ### Unbind Service Instance +```sh curl 'http://username:password@localhost:3000/v2/service_instances/instance1/service_bindings/binding1?service_id=service-id-here&plan_id=plan-id-here' -X DELETE -H "X-Broker-API-Version: 2.4" +``` ### Deprovision Instance + +```sh curl 'http://username:password@localhost:3000/v2/service_instances/instance1?service_id=b6844738-382b-4a9e-9f80-2ff5049d512f&plan_id=fb740fd7-2029-467a-9256-63ecd882f11c' -X DELETE -H "X-Broker-API-Version: 2.4" +``` ## Windows Service installation -Use the following steps to install a windows service for the broker. Make sure you copy the binary -and config file to "c:\cf-mssql-broker" +Use the following steps to install a windows service for the broker. Make sure you copy the binary and config file to "c:\cf-mssql-broker" +```sh choco install nssm $installDir = $env:systemdrive+'\cf-mssql-broker' @@ -115,10 +133,54 @@ nssm set cf-mssql-broker AppStdout $logPath nssm set cf-mssql-broker AppStderr $logPath nssm start cf-mssql-broker +``` ## Integrating into a Cloud Foundry deployemnt You need admin access to a Cloud Foundry deployment to add a new service broker. +```sh cf create-service-broker mssql-broker1 username password http://192.168.1.10:3000 cf enable-service-access mssql-dev +``` +## Binding credentials exmaple + +VCAP_SERVICES env variable for a CF application with a mssql service binding will contin the crednetials to the SQL Server. The folowing [credential fields](https://github.com/hpcloud/cf-mssql-broker/blob/master/mssql_binding_credentials.go) will be used: + * "host" - IP address or host of the SQL Server + * "port" - The listening TCP port number + * "name" - Database name + * "username" - User with credentials to the database + * "password" - Password for the username + + +Example: +```sh +cf env got +Getting env variables for app got in org diego / space diego as admin... +OK + +System-Provided: +{ + "VCAP_SERVICES": { + "mssql-dev": [ + { + "credentials": { + "host": "10.0.0.93", + "password": "DxdgJcdqzAbssMP7w_f7qsPtTlWklFhHHXLTw5_IlUI=qwerASF1234!@#$", + "port": 1433, + "name": "cf-6536b7c1-6aa6-455f-9b54-fbe8de63053f" + "username": "cf-6536b7c1-6aa6-455f-9b54-fbe8de63053f-856da771-d14b-4fad-a902-1eb02ff20c61" + }, + "label": "mssql-dev", + "name": "db1", + "plan": "default", + "tags": [ + "mssql", + "relational" + ] + } + ] + } +} +... +``` \ No newline at end of file diff --git a/cf_mssql_broker_config.json b/cf_mssql_broker_config.json index cefc799..b451dc0 100644 --- a/cf_mssql_broker_config.json +++ b/cf_mssql_broker_config.json @@ -37,5 +37,6 @@ }, "servedMssqlBindingHostname" : "192.168.1.10", - "servedMssqlBindingPort" : 1433 + "servedMssqlBindingPort" : 1433, + "grantDbOwnerForBindings": false } \ No newline at end of file diff --git a/cf_mssql_broker_noodbc_config.json b/cf_mssql_broker_noodbc_config.json index 0940960..61cda39 100644 --- a/cf_mssql_broker_noodbc_config.json +++ b/cf_mssql_broker_noodbc_config.json @@ -38,5 +38,6 @@ }, "servedMssqlBindingHostname" : "192.168.1.10", - "servedMssqlBindingPort" : 38017 + "servedMssqlBindingPort" : 38017, + "grantDbOwnerForBindings": false } \ No newline at end of file diff --git a/cf_mssql_broker_tcpodbc_config.json b/cf_mssql_broker_tcpodbc_config.json index 7ea708f..1f094b0 100644 --- a/cf_mssql_broker_tcpodbc_config.json +++ b/cf_mssql_broker_tcpodbc_config.json @@ -38,5 +38,6 @@ }, "servedMssqlBindingHostname" : "192.168.1.10", - "servedMssqlBindingPort" : 1433 + "servedMssqlBindingPort" : 1433, + "grantDbOwnerForBindings": false } \ No newline at end of file diff --git a/config/config.go b/config/config.go index 6302d95..5ecc666 100644 --- a/config/config.go +++ b/config/config.go @@ -9,15 +9,16 @@ import ( ) type Config struct { - DbIdentifierPrefix string `json:"dbIdentifierPrefix"` - ListeningAddr string `json:"listeningAddr"` - LogLevel string `json:"logLevel"` - Crednetials brokerapi.BrokerCredentials `json:"brokerCredentials"` - ServiceCatalog []brokerapi.Service `json:"serviceCatalog"` - BrokerGoSqlDriver string `json:"brokerGoSqlDriver"` - BrokerMssqlConnection map[string]string `json:"brokerMssqlConnection"` - ServedBindingHostname string `json:"servedMssqlBindingHostname"` - ServedBindingPort int `json:"servedMssqlBindingPort"` + DbIdentifierPrefix string `json:"dbIdentifierPrefix"` + ListeningAddr string `json:"listeningAddr"` + LogLevel string `json:"logLevel"` + Crednetials brokerapi.BrokerCredentials `json:"brokerCredentials"` + ServiceCatalog []brokerapi.Service `json:"serviceCatalog"` + BrokerGoSqlDriver string `json:"brokerGoSqlDriver"` + BrokerMssqlConnection map[string]string `json:"brokerMssqlConnection"` + ServedBindingHostname string `json:"servedMssqlBindingHostname"` + ServedBindingPort int `json:"servedMssqlBindingPort"` + GrantDbOwnerForBindings bool `json:"grantDbOwnerForBindings"` } func LoadFromFile(path string) (*Config, error) { diff --git a/mssql_service_broker.go b/mssql_service_broker.go index 329ed15..1f227e0 100644 --- a/mssql_service_broker.go +++ b/mssql_service_broker.go @@ -111,7 +111,7 @@ func (*mssqlServiceBroker) Bind(instanceID, bindingID string) (interface{}, erro return nil, brokerapi.ErrBindingAlreadyExists } - err = mssqlProv.CreateUser(databaseName, username, password) + err = mssqlProv.CreateUser(databaseName, username, password, brokerConfig.GrantDbOwnerForBindings) if err != nil { logger.Fatal("provisioner-error", err) } diff --git a/provisioner/provisioner.go b/provisioner/provisioner.go index fe60f68..df797a0 100644 --- a/provisioner/provisioner.go +++ b/provisioner/provisioner.go @@ -16,6 +16,20 @@ var createDatabaseTemplate = []string{ "create database [%[1]v] containment = partial", } +// fmt template paramters: 1.databaseId +var createRoleWithFullDboAccessTemplate = []string{ + "use [%[1]v]", + "create role [cf_bindings]", + `grant alter, control, create sequence, + create sequence, delete, execute, + insert, references, select, update, + view change tracking, view definition, + take ownership + on schema::[dbo] to [cf_bindings] + `, + "use master", +} + // fmt template parameters: 1.databaseId var deleteDatabaseTemplate = []string{ "use master", @@ -23,11 +37,12 @@ var deleteDatabaseTemplate = []string{ "drop database [%[1]v]", } -// fmt template parameters: 1.databaseId, 2.userId, 3.password +// fmt template parameters: 1.databaseId, 2.userId, 3.password, 4.dbRole var createUserTemplate = []string{ "use [%[1]v]", - "create user [%[2]v] with password='%[3]v'", - "alter role [db_owner] add member [%[2]v]", + "create user [%[2]v] with password='%[3]v', default_schema=[dbo] ", + "alter role [%[4]v] add member [%[2]v]", + "alter role [cf_bindings] add member [%[2]v]", "use master", } @@ -85,15 +100,29 @@ func (provisioner *MssqlProvisioner) Init() error { } func (provisioner *MssqlProvisioner) CreateDatabase(databaseId string) error { - return provisioner.executeTemplateWithoutTx(createDatabaseTemplate, databaseId) + err := provisioner.executeTemplateWithoutTx(createDatabaseTemplate, databaseId) + if err != nil { + return err + } + + err = provisioner.executeTemplateWithTx(createRoleWithFullDboAccessTemplate, databaseId) + if err != nil { + return err + } + + return nil } func (provisioner *MssqlProvisioner) DeleteDatabase(databaseId string) error { return provisioner.executeTemplateWithoutTx(deleteDatabaseTemplate, databaseId) } -func (provisioner *MssqlProvisioner) CreateUser(databaseId, userId, password string) error { - return provisioner.executeTemplateWithoutTx(createUserTemplate, databaseId, userId, password) +func (provisioner *MssqlProvisioner) CreateUser(databaseId, userId, password string, grantDbOwner bool) error { + role := "db_ddladmin" + if grantDbOwner { + role = "db_owner" + } + return provisioner.executeTemplateWithoutTx(createUserTemplate, databaseId, userId, password, role) } func (provisioner *MssqlProvisioner) DeleteUser(databaseId, userId string) error { diff --git a/provisioner/provisioner_test.go b/provisioner/provisioner_test.go index 65b31b7..d9a9701 100644 --- a/provisioner/provisioner_test.go +++ b/provisioner/provisioner_test.go @@ -102,7 +102,7 @@ func TestDeleteUserOdbcDriver(t *testing.T) { } // Act - err = mssqlProv.CreateUser(dbName, userNanme, "passwordAa_0") + err = mssqlProv.CreateUser(dbName, userNanme, "passwordAa_0", true) // Assert if err != nil { @@ -138,7 +138,7 @@ func TestCreateUserOdbcDriver(t *testing.T) { if err != nil { t.Errorf("Database create error, %v", err) } - err = mssqlProv.CreateUser(dbName, userNanme, "passwordAa_0") + err = mssqlProv.CreateUser(dbName, userNanme, "passwordAa_0", false) if err != nil { t.Errorf("User create error, %v", err) }