github.com/adecaro/fabric-ca@v2.0.0-alpha+incompatible/lib/server/db/postgres/postgres.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package postgres 8 9 import ( 10 "context" 11 "fmt" 12 "regexp" 13 "strings" 14 15 "github.com/cloudflare/cfssl/log" 16 "github.com/hyperledger/fabric-ca/lib/server/db" 17 "github.com/hyperledger/fabric-ca/lib/server/db/util" 18 "github.com/hyperledger/fabric-ca/lib/tls" 19 "github.com/hyperledger/fabric/common/metrics" 20 "github.com/jmoiron/sqlx" 21 _ "github.com/lib/pq" // import to support Postgres 22 "github.com/pkg/errors" 23 ) 24 25 // Postgres defines PostgreSQL database 26 type Postgres struct { 27 SqlxDB db.FabricCADB 28 TLS *tls.ClientTLSConfig 29 CAName string 30 MetricsProvider metrics.Provider 31 32 datasource string 33 dbName string 34 } 35 36 // NewDB create a PosgreSQL database 37 func NewDB(datasource, caName string, clientTLSConfig *tls.ClientTLSConfig, metricsProvider metrics.Provider) *Postgres { 38 log.Debugf("Using postgres database, connecting to database...") 39 return &Postgres{ 40 datasource: datasource, 41 TLS: clientTLSConfig, 42 CAName: caName, 43 MetricsProvider: metricsProvider, 44 } 45 } 46 47 // Connect connects to a PostgreSQL server 48 func (p *Postgres) Connect() error { 49 datasource := p.datasource 50 clientTLSConfig := p.TLS 51 52 p.dbName = util.GetDBName(datasource) 53 dbName := p.dbName 54 log.Debugf("Database Name: %s", dbName) 55 56 if strings.Contains(dbName, "-") || strings.HasSuffix(dbName, ".db") { 57 return errors.Errorf("Database name '%s' cannot contain any '-' or end with '.db'", dbName) 58 } 59 60 if clientTLSConfig.Enabled { 61 if len(clientTLSConfig.CertFiles) == 0 { 62 return errors.New("No trusted root certificates for TLS were provided") 63 } 64 65 root := clientTLSConfig.CertFiles[0] 66 datasource = fmt.Sprintf("%s sslrootcert=%s", datasource, root) 67 68 cert := clientTLSConfig.Client.CertFile 69 key := clientTLSConfig.Client.KeyFile 70 datasource = fmt.Sprintf("%s sslcert=%s sslkey=%s", datasource, cert, key) 71 } 72 73 dbNames := []string{dbName, "postgres", "template1"} 74 var sqlxdb *sqlx.DB 75 var err error 76 77 for _, dbName := range dbNames { 78 connStr := getConnStr(datasource, dbName) 79 log.Debugf("Connecting to PostgreSQL server, using connection string: %s", util.MaskDBCred(connStr)) 80 81 sqlxdb, err = sqlx.Connect("postgres", connStr) 82 if err == nil { 83 break 84 } 85 log.Warningf("Failed to connect to database '%s'", dbName) 86 } 87 88 if err != nil { 89 return errors.Errorf("Failed to connect to Postgres database. Postgres requires connecting to a specific database, the following databases were tried: %s. Please create one of these database before continuing", dbNames) 90 } 91 92 p.SqlxDB = db.New(sqlxdb, p.CAName, p.MetricsProvider) 93 return nil 94 } 95 96 // PingContext pings the database 97 func (p *Postgres) PingContext(ctx context.Context) error { 98 err := p.SqlxDB.PingContext(ctx) 99 if err != nil { 100 return errors.Wrap(err, "Failed to ping to Postgres database") 101 } 102 return nil 103 } 104 105 // Create creates database and tables 106 func (p *Postgres) Create() (*db.DB, error) { 107 db, err := p.CreateDatabase() 108 if err != nil { 109 return nil, err 110 } 111 err = p.CreateTables() 112 if err != nil { 113 return nil, err 114 } 115 return db, nil 116 } 117 118 // CreateDatabase creates database 119 func (p *Postgres) CreateDatabase() (*db.DB, error) { 120 dbName := p.dbName 121 datasource := p.datasource 122 err := p.createDatabase() 123 if err != nil { 124 return nil, errors.Wrap(err, "Failed to create Postgres database") 125 } 126 127 log.Debugf("Connecting to database '%s', using connection string: '%s'", dbName, util.MaskDBCred(datasource)) 128 sqlxdb, err := sqlx.Open("postgres", datasource) 129 if err != nil { 130 return nil, errors.Wrapf(err, "Failed to open database '%s' in Postgres server", dbName) 131 } 132 p.SqlxDB = db.New(sqlxdb, p.CAName, p.MetricsProvider) 133 134 return p.SqlxDB.(*db.DB), nil 135 } 136 137 // CreateTables creates table 138 func (p *Postgres) CreateTables() error { 139 err := p.createTables() 140 if err != nil { 141 return errors.Wrap(err, "Failed to create Postgres tables") 142 } 143 return nil 144 } 145 146 func (p *Postgres) createDatabase() error { 147 dbName := p.dbName 148 log.Debugf("Creating Postgres Database (%s) if it does not exist...", dbName) 149 150 query := "CREATE DATABASE " + dbName 151 _, err := p.SqlxDB.Exec("CreateDatabase", query) 152 if err != nil { 153 if !strings.Contains(err.Error(), fmt.Sprintf("database \"%s\" already exists", dbName)) { 154 return errors.Wrap(err, "Failed to execute create database query") 155 } 156 } 157 158 return nil 159 } 160 161 // createPostgresDB creates postgres database 162 func (p *Postgres) createTables() error { 163 db := p.SqlxDB 164 log.Debug("Creating users table if it does not exist") 165 if _, err := db.Exec("CreateUsersTable", "CREATE TABLE IF NOT EXISTS users (id VARCHAR(255), token bytea, type VARCHAR(256), affiliation VARCHAR(1024), attributes TEXT, state INTEGER, max_enrollments INTEGER, level INTEGER DEFAULT 0, incorrect_password_attempts INTEGER DEFAULT 0)"); err != nil { 166 return errors.Wrap(err, "Error creating users table") 167 } 168 log.Debug("Creating affiliations table if it does not exist") 169 if _, err := db.Exec("CreateAffiliationTable", "CREATE TABLE IF NOT EXISTS affiliations (name VARCHAR(1024) NOT NULL UNIQUE, prekey VARCHAR(1024), level INTEGER DEFAULT 0)"); err != nil { 170 return errors.Wrap(err, "Error creating affiliations table") 171 } 172 log.Debug("Creating certificates table if it does not exist") 173 if _, err := db.Exec("CreateCertificatesTable", "CREATE TABLE IF NOT EXISTS certificates (id VARCHAR(255), serial_number bytea NOT NULL, authority_key_identifier bytea NOT NULL, ca_label bytea, status bytea NOT NULL, reason int, expiry timestamp, revoked_at timestamp, pem bytea NOT NULL, level INTEGER DEFAULT 0, PRIMARY KEY(serial_number, authority_key_identifier))"); err != nil { 174 return errors.Wrap(err, "Error creating certificates table") 175 } 176 log.Debug("Creating credentials table if it does not exist") 177 if _, err := db.Exec("CreateCredentialsTable", "CREATE TABLE IF NOT EXISTS credentials (id VARCHAR(255), revocation_handle bytea NOT NULL, cred bytea NOT NULL, ca_label bytea, status bytea NOT NULL, reason int, expiry timestamp, revoked_at timestamp, level INTEGER DEFAULT 0, PRIMARY KEY(revocation_handle))"); err != nil { 178 return errors.Wrap(err, "Error creating credentials table") 179 } 180 log.Debug("Creating revocation_authority_info table if it does not exist") 181 if _, err := db.Exec("CreateRevocationAuthorityTable", "CREATE TABLE IF NOT EXISTS revocation_authority_info (epoch INTEGER, next_handle INTEGER, lasthandle_in_pool INTEGER, level INTEGER DEFAULT 0, PRIMARY KEY(epoch))"); err != nil { 182 return errors.Wrap(err, "Error creating revocation_authority_info table") 183 } 184 log.Debug("Creating nonces table if it does not exist") 185 if _, err := db.Exec("CreateNoncesTable", "CREATE TABLE IF NOT EXISTS nonces (val VARCHAR(255) NOT NULL UNIQUE, expiry timestamp, level INTEGER DEFAULT 0, PRIMARY KEY (val))"); err != nil { 186 return errors.Wrap(err, "Error creating nonces table") 187 } 188 log.Debug("Creating properties table if it does not exist") 189 if _, err := db.Exec("CreatePropertiesTable", "CREATE TABLE IF NOT EXISTS properties (property VARCHAR(255), value VARCHAR(256), PRIMARY KEY(property))"); err != nil { 190 return errors.Wrap(err, "Error creating properties table") 191 } 192 _, err := db.Exec("CreatePropertiesTable", db.Rebind("INSERT INTO properties (property, value) VALUES ('identity.level', '0'), ('affiliation.level', '0'), ('certificate.level', '0'), ('credential.level', '0'), ('rcinfo.level', '0'), ('nonce.level', '0')")) 193 if err != nil { 194 if !strings.Contains(err.Error(), "duplicate key") { 195 return err 196 } 197 } 198 return nil 199 } 200 201 // GetConnStr gets connection string without database 202 func getConnStr(datasource string, dbname string) string { 203 re := regexp.MustCompile(`(dbname=)([^\s]+)`) 204 connStr := re.ReplaceAllString(datasource, fmt.Sprintf("dbname=%s", dbname)) 205 return connStr 206 }