github.com/hyperledger-gerrit-archive/fabric-ca@v2.0.0-alpha.0.20190916143245-4cd4192f0366+incompatible/lib/server/db/mysql/mysql.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package mysql 8 9 import ( 10 "context" 11 "regexp" 12 "strings" 13 14 "github.com/cloudflare/cfssl/log" 15 "github.com/go-sql-driver/mysql" 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/bccsp" 20 "github.com/jmoiron/sqlx" 21 "github.com/pkg/errors" 22 ) 23 24 var ( 25 re = regexp.MustCompile(`\/([0-9,a-z,A-Z$_]+)`) 26 ) 27 28 // Mysql defines MySQL database 29 type Mysql struct { 30 SqlxDB db.FabricCADB 31 TLS *tls.ClientTLSConfig 32 CSP bccsp.BCCSP 33 CAName string 34 Metrics *db.Metrics 35 36 datasource string 37 dbName string 38 } 39 40 // NewDB create a MySQL database 41 func NewDB( 42 datasource, 43 caName string, 44 clientTLSConfig *tls.ClientTLSConfig, 45 csp bccsp.BCCSP, 46 metrics *db.Metrics, 47 ) *Mysql { 48 log.Debugf("Using MySQL database, connecting to database...") 49 return &Mysql{ 50 TLS: clientTLSConfig, 51 CSP: csp, 52 datasource: datasource, 53 CAName: caName, 54 Metrics: metrics, 55 } 56 } 57 58 // Connect connects to a MySQL server 59 func (m *Mysql) Connect() error { 60 datasource := m.datasource 61 clientTLSConfig := m.TLS 62 63 m.dbName = util.GetDBName(datasource) 64 log.Debugf("Database Name: %s", m.dbName) 65 66 connStr := re.ReplaceAllString(datasource, "/") 67 68 if clientTLSConfig.Enabled { 69 tlsConfig, err := tls.GetClientTLSConfig(clientTLSConfig, m.CSP) 70 if err != nil { 71 return errors.WithMessage(err, "Failed to get client TLS for MySQL") 72 } 73 74 mysql.RegisterTLSConfig("custom", tlsConfig) 75 } 76 77 log.Debugf("Connecting to MySQL server, using connection string: %s", util.MaskDBCred(connStr)) 78 sqlxdb, err := sqlx.Connect("mysql", connStr) 79 if err != nil { 80 return errors.Wrap(err, "Failed to connect to MySQL database") 81 } 82 83 m.SqlxDB = db.New(sqlxdb, m.CAName, m.Metrics) 84 return nil 85 } 86 87 // PingContext pings the database 88 func (m *Mysql) PingContext(ctx context.Context) error { 89 err := m.SqlxDB.PingContext(ctx) 90 if err != nil { 91 return errors.Wrap(err, "Failed to ping to MySQL database") 92 } 93 return nil 94 } 95 96 // Create creates database and tables 97 func (m *Mysql) Create() (*db.DB, error) { 98 db, err := m.CreateDatabase() 99 if err != nil { 100 return nil, err 101 } 102 err = m.CreateTables() 103 if err != nil { 104 return nil, err 105 } 106 return db, nil 107 } 108 109 // CreateDatabase creates database 110 func (m *Mysql) CreateDatabase() (*db.DB, error) { 111 datasource := m.datasource 112 dbName := m.dbName 113 err := m.createDatabase() 114 if err != nil { 115 return nil, errors.Wrap(err, "Failed to create MySQL database") 116 } 117 118 log.Debugf("Connecting to database '%s', using connection string: '%s'", dbName, util.MaskDBCred(datasource)) 119 sqlxdb, err := sqlx.Open("mysql", datasource) 120 if err != nil { 121 return nil, errors.Wrapf(err, "Failed to open database (%s) in MySQL server", dbName) 122 } 123 124 m.SqlxDB = db.New(sqlxdb, m.CAName, m.Metrics) 125 126 return m.SqlxDB.(*db.DB), nil 127 } 128 129 // CreateTables creates table 130 func (m *Mysql) CreateTables() error { 131 err := m.createTables() 132 if err != nil { 133 return errors.Wrap(err, "Failed to create MySQL tables") 134 } 135 return nil 136 } 137 138 func (m *Mysql) createDatabase() error { 139 dbName := m.dbName 140 log.Debugf("Creating MySQL Database (%s) if it does not exist...", dbName) 141 142 _, err := m.SqlxDB.Exec("CreateDatabase", "CREATE DATABASE IF NOT EXISTS "+dbName) 143 if err != nil { 144 return errors.Wrap(err, "Failed to execute create database query") 145 } 146 147 return nil 148 } 149 150 func (m *Mysql) createTables() error { 151 db := m.SqlxDB 152 log.Debug("Creating users table if it doesn't exist") 153 if _, err := db.Exec("CreateUsersTable", "CREATE TABLE IF NOT EXISTS users (id VARCHAR(255) NOT NULL, token blob, type VARCHAR(256), affiliation VARCHAR(1024), attributes TEXT, state INTEGER, max_enrollments INTEGER, level INTEGER DEFAULT 0, incorrect_password_attempts INTEGER DEFAULT 0, PRIMARY KEY (id)) DEFAULT CHARSET=utf8 COLLATE utf8_bin"); err != nil { 154 return errors.Wrap(err, "Error creating users table") 155 } 156 log.Debug("Creating affiliations table if it doesn't exist") 157 if _, err := db.Exec("CreateAffiliationsTable", "CREATE TABLE IF NOT EXISTS affiliations (id INT NOT NULL AUTO_INCREMENT, name VARCHAR(1024) NOT NULL, prekey VARCHAR(1024), level INTEGER DEFAULT 0, PRIMARY KEY (id)) DEFAULT CHARSET=utf8 COLLATE utf8_bin"); err != nil { 158 return errors.Wrap(err, "Error creating affiliations table") 159 } 160 log.Debug("Creating index on 'name' in the affiliations table") 161 if _, err := db.Exec("CreateAffiliationsIndex", "CREATE INDEX name_index on affiliations (name)"); err != nil { 162 if !strings.Contains(err.Error(), "Error 1061") { // Error 1061: Duplicate key name, index already exists 163 return errors.Wrap(err, "Error creating index on affiliations table") 164 } 165 } 166 log.Debug("Creating certificates table if it doesn't exist") 167 if _, err := db.Exec("CreateCertificatesTable", "CREATE TABLE IF NOT EXISTS certificates (id VARCHAR(255), serial_number varbinary(128) NOT NULL, authority_key_identifier varbinary(128) NOT NULL, ca_label varbinary(128), status varbinary(128) NOT NULL, reason int, expiry timestamp DEFAULT 0, revoked_at timestamp DEFAULT 0, pem varbinary(4096) NOT NULL, level INTEGER DEFAULT 0, PRIMARY KEY(serial_number, authority_key_identifier)) DEFAULT CHARSET=utf8 COLLATE utf8_bin"); err != nil { 168 return errors.Wrap(err, "Error creating certificates table") 169 } 170 log.Debug("Creating credentials table if it doesn't exist") 171 if _, err := db.Exec("CreateCredentialsTable", "CREATE TABLE IF NOT EXISTS credentials (id VARCHAR(255), revocation_handle varbinary(128) NOT NULL, cred varbinary(4096) NOT NULL, ca_label varbinary(128), status varbinary(128) NOT NULL, reason int, expiry timestamp DEFAULT 0, revoked_at timestamp DEFAULT 0, level INTEGER DEFAULT 0, PRIMARY KEY(revocation_handle)) DEFAULT CHARSET=utf8 COLLATE utf8_bin"); err != nil { 172 return errors.Wrap(err, "Error creating credentials table") 173 } 174 log.Debug("Creating revocation_authority_info table if it does not exist") 175 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)) DEFAULT CHARSET=utf8 COLLATE utf8_bin"); err != nil { 176 return errors.Wrap(err, "Error creating revocation_authority_info table") 177 } 178 log.Debug("Creating nonces table if it does not exist") 179 if _, err := db.Exec("CreateNoncesTable", "CREATE TABLE IF NOT EXISTS nonces (val VARCHAR(255) NOT NULL, expiry timestamp, level INTEGER DEFAULT 0, PRIMARY KEY (val)) DEFAULT CHARSET=utf8 COLLATE utf8_bin"); err != nil { 180 return errors.Wrap(err, "Error creating nonces table") 181 } 182 log.Debug("Creating properties table if it does not exist") 183 if _, err := db.Exec("CreatePropertiesTable", "CREATE TABLE IF NOT EXISTS properties (property VARCHAR(255), value VARCHAR(256), PRIMARY KEY(property)) DEFAULT CHARSET=utf8 COLLATE utf8_bin"); err != nil { 184 return errors.Wrap(err, "Error creating properties table") 185 } 186 _, 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')")) 187 if err != nil { 188 if !strings.Contains(err.Error(), "1062") { // MySQL error code for duplicate entry 189 return err 190 } 191 } 192 return nil 193 }