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  }