Closed RPuffer closed 5 years ago
@RPuffer I haven't tried my driver with SSL. But I will see what I can dig up. Thanks for reporting the issue.
@asifjalil No problem, and I appreciate the quick response! I will continue to investigate as well, but yeah let me know if you find anything
@RPuffer I had to configure the DB2 Instance to use SSL first. To do that I followed the steps in this article from ToadWorld. Here are my steps for creating a self-signed certificate (this is on Linux):
$ cd ~
$ mkdir cert
$ cd cert
$ /opt/ibm/db2/V11.1/gskit/bin/gsk8capicmd_64 -keydb -create -db "server.kdb" -pw "Passw0rd" -stash
$ /opt/ibm/db2/V11.1/gskit/bin/gsk8capicmd_64 -cert -create -db "server.kdb" -pw "Passw0rd" -label "mylabel" -dn "CN=testcompany" -size 2048 -sigalg SHA256_WITH_RSA
$ /opt/ibm/db2/V11.1/gskit/bin/gsk8capicmd_64 -cert -extract -db "server.kdb" -pw "Passw0rd" -label "mylabel" -target "server.arm" -format ascii
I then added a new port to /etc/services file for the SSL connection:
$ sudo vi /etc/services # add db2cs_asif 50001/tcp
Then I configured the DB2 Instance to use SSL and the certificate. Please note that this requires DB2 SYSADM privilege because you will need to update the instance configuration and then recycle (DB2 Instance name in this case is called asif):
$ db2 update dbm cfg using SSL_SVR_KEYDB /home/asif/certs/server.kdb
$ db2 update dbm cfg using SSL_SVR_STASH /home/asif/certs/server.sth
$ db2 update dbm cfg using SSL_SVR_LABEL mylabel
$ db2 update dbm cfg using SSL_SVCENAME db2cs_asif
$ db2set -i asif DB2COMM=TCPIP,SSL
$ db2stop
$ db2start
Once DB2 has been configured, you can use this connection string in Go to connect to the database sample that is under DB2 Instance asif:
var (
database = "sample"
schema = "asif"
host = "localhost"
port = 50001
protocol = "tcpip"
username = "asif"
password = "****"
certFile = "/home/asif/certs/server.arm"
)
connStr := fmt.Sprintf(`DATABASE=%s; CurrentSchema=%s; HOSTNAME=%s; PORT=%d;
PROTOCOL=%s; UID=%s; PWD=%s; Security=SSL;SSLServerCertificate=%s`,
database, schema, host, port, protocol, username, password, certFile)
Please note that for the certificate file, I am using the .arm file that I created in the certificate step. Hope that helps.
@asifjalil Interesting, well my issue must be something with our DB2 and/or firewall setup... or I'm using the wrong certificate. Thank you for investigating! I'm working with our DB Admins to diagnose the problem so this information is very useful.
Then, as it seems it is not an issue related to your driver I will go ahead and close this issue, thanks again for your help!
@asifjalil Actually, just want to make sure we're on the same page. When setting up you SSL connection, are you successfully able to use a Ping
or Query
functions on the *DB
?
I find that my issue arrises only when I try to ping the Database, or execute a query. But, when I use sql.Open(...)
that seems to work just fine.
@RPuffer I was able to run the following:
connStr := fmt.Sprintf(`DATABASE=%s; CurrentSchema=%s; HOSTNAME=%s; PORT=%d;
PROTOCOL=%s; UID=%s; PWD=%s; Security=SSL;SSLServerCertificate=%s`,
database, schema, host, port, protocol, username, password, certFile)
qry := "SELECT 1 FROM syscat.tables where tabschema='ASIF' Fetch first 1 row only"
db, err := sql.Open("cli", connStr)
if err != nil {
log.Fatal(err)
}
defer db.Close()
log.Println("Testing QueryRow.Scan()")
log.Println(strings.Repeat("#", 40))
err = db.QueryRow(qry).Scan(&val)
switch {
case err == sql.ErrNoRows:
fmt.Printf("%q: empty resultset\n", qry)
case err != nil:
log.Fatal(err)
default:
fmt.Printf("%q: got %d\n", qry, val)
}
log.Println("Testing Query")
log.Println(strings.Repeat("#", 40))
rows, err := db.Query(qry)
switch {
case err == sql.ErrNoRows:
fmt.Printf("%q: empty resultset\n", qry)
case err != nil:
log.Fatalf("%q: %v\n", qry, err)
default:
for rows.Next() {
if err := rows.Scan(&val); err != nil {
log.Fatalf("%q: rows.Scan failed: %v\n", qry, err)
}
fmt.Printf("%q: got %d\n", qry, val)
}
}
Result:
2019/04/29 15:27:26 Testing QueryRow.Scan()
2019/04/29 15:27:26 ########################################
"SELECT 1 FROM syscat.tables where tabschema='ASIF' fetch first 1 row only": got 1
2019/04/29 15:27:27 Testing Query
2019/04/29 15:27:27 ########################################
"SELECT 1 FROM syscat.tables where tabschema='ASIF' fetch first 1 row only": got 1
I am using local client and database. That is, there is no firewall.
@asifjalil hmm yeah that's pretty much exactly what I'm doing.. I have no idea why it's not working with Go. I've tested the SSL DB connection with a Node.js app as well and that seems to have no issues. Must be a firewall or network issue I guess. Thanks again for your help.
@RPuffer Hmm. It is surprising that Node.js app works but this Go DB2 driver don't because both underneath uses the same ODBC/CLI driver to interact with DB2. Maybe the issue is something along this line.
@asifjalil yeah, it seems odd that Node.js works.. and I have some more experienced DB admins looking into potential configuration issues. I did find an interesting case doing some more testing with the Node.js side.
So, in the Node app, when i remove the Security=SSL; SSLServerCertificate=%s
parameters from the connection string, I get the same error as I am seeing in Go. This makes me wonder if maybe, in the Go app, those parameters in the connection string are not getting applied for some reason?
If you remove the Security=SSL;SSLServerCertificate=%s
params from your SSL example, are you still able to Open
and Query
the DB2 instance you set up?
@RPuffer If I remove Security=SSL;SSLServerCertificate=%s
, then I get the following error:
2019/04/29 19:51:12 Testing QueryRow.Scan()
2019/04/29 19:51:12 ########################################
2019/04/29 19:51:12 database/sql/driver: [IBM][CLI Driver] SQL30081N A communication error has been detected. Communication protocol being used: "TCP/IP". Communication API being used: "SOCKETS". Location where the error was detected: "127.0.0.1". Communication function detecting the error: "recv". Protocol specific error code(s): "104", "*", "0". SQLSTATE=08001
According to SQL30081N, the error means the following:
* If "<protocol>" = TCP/IP, "<function>" = recv,
"<rc1>" = ECONNRESET/WSAECONNRESET, AIX (73), Windows
(10054), linux (104), SUN (131), HP (232), the connection was reset
by the remote side executing a "hard" or "abortive" close. Possible
causes:
* The connection might have been closed by the remote gateway or
server at the TCP/IP level (eg. firewall problem, power failure,
network failure).
* Client side connection pooling is enabled and is not handling
connection failures. Code the application to retry a connection if
a failure is received when connecting to the database and
connection pooling is enabled.
* Might have been caused by a thread timeout on the host machine.
Check the system log on the host for the presence of the IDTHTOIN
message. Adjust the setting if applicable. If this cannot be
adjusted, either disable connection pooling on the gateway, or
ensure all objects are properly closed (eg. cursors with hold when
the application completes its operation).
* The database agent at the server was forced off by the database
administrator.
* The database agent at the server was terminated due to an abnormal
termination of a key database manager process.
Not easy to understand but db2diag.log shows this:
2019-04-29-19.51.12.672923+000 I7194435E493 LEVEL: Error
PID : 6497 TID : 140484932527872 PROC : db2sysc
INSTANCE: asif NODE : 000 DB : SAMPLE
APPHDL : 0-344
HOSTNAME: flux
EDUID : 52 EDUNAME: db2agent ()
FUNCTION: DB2 UDB, common communication, sqlccMapSSLErrorToDB2Error, probe:30
MESSAGE : DIA3604E The SSL function "gsk_secure_soc_init" failed with the
return code "410" in "sqlccSSLSocketSetup".
That makes more sense because I tried to connect to DB2's SSL port without using SSL option.
Question: the DB2 instance you are trying to connect to does it have this set?
$ db2set -all [i] DB2COMM=TCPIP,SSL
@asifjalil interesting, so yeah that's the error I'm getting - I will have to double check with our admins if the DB2 instance contains that set (as i don't have personal access, which is making troubleshooting a lot more difficult haha).
Well.. I just had a successful local run. I've been dealing with a security issue where our antivirus has been blocking any attempts to run local Go binaries so I've been spinning up a Docker container every-time I want to test the application. The antivirus will eventually allow for execution of the binary after 30min - 1 hour as long as it's cleared as 'not malware'. But since this process takes forever I haven't actually run the binary outside of a Docker container in a while.
So, now, the issue seems to be with the Container I'm spinning up. Do you know if there's any ports specific to the DB communication I would need to expose? Or maybe the odbc-cli package I'm using in the Docker container is insufficient .
I'm pretty sure the Node.js package I'm using ibm_db installs some sort of DB2 CLI client as well so maybe that is why it just worked on my local machine?
I will do some more investigation tomorrow.
@RPuffer yes odbc-cli package could be the issue. I only tested this Go driver using DB2 CLI driver. Try the DB2 driver from here. Also, someone contributed this docker file for this driver. Maybe that will help.
Well, I got it working in the Docker image! It seemed to be a combination of referencing the contributed Docker file for configuration items I might have missed, and changing of the base image. I'm still not sure exactly what was going wrong before but it looks like I'm up and running for now. Thanks again for all your help!
EDIT
After further review of my changes (and in case anyone else has issues in the future) - it appears that there was just a few extra configurations necessary on my docker image to make the odbc-cli
package work correctly. Sharing my Dockerfile used (updates indicated with comments) in case anyone else runs into this issue.
FINAL EDIT
Turns out our firewall team just messed up my request to open the SSL ports for the DB calls š¤¦āā. They must have silently fixed it which is why I got the successful run randomly. Anyways, I've updated the Dockerfile below as a good example of how to use this cli package for a go-module
project without needing the db2 driver tar file in the repository. Another benefit of this Dockerfile is it is very lightweight due to using the debian:slim-stretch
base image. The odbc-cli
download link is the same as used in the Node.js package ibm_db
so it should be very reliable. Also included an example makefile to show the build env vars. @asifjalil feel free to use this as an example for a go-module
project Dockerfile with a dynamic install of the odbc-cli
driver.
Example Dockerfile
FROM golang:1.11-stretch AS builder
ENV DB2DIR="$HOME/sqllib"
WORKDIR /tmp/app
# IF root certs needed for go mod download
RUN curl \
-o "/usr/share/ca-certificates/āāāāā" "āāāāāāāāāāāāāāā" \
-o "/usr/share/ca-certificates/āāāāā" "āāāāāāāāāāāāāāā" \
&& echo "āāāāā" >> /etc/ca-certificates.conf \
&& echo "āāāāā" >> /etc/ca-certificates.conf
RUN update-ca-certificates
# Download Driver for compilation
RUN apt-get update \
&& apt-get install -y git gcc libxml2 \
&& curl -o "db2clidriver.tgz" "https://public.dhe.ibm.com/ibmdl/export/pub/software/data/db2/drivers/odbc_cli/linuxx64_odbc_cli.tar.gz" \
&& tar xf "db2clidriver.tgz" \
&& mv "clidriver" "${DB2DIR}" \
&& rm -r *.tgz \
&& ln -sv ${DB2DIR}/lib/* /usr/lib/ \
&& ln -sv ${DB2DIR}/include/* /usr/include/
# Download dependencies & copy app
COPY go.mod go.sum ./
RUN go mod download
COPY ./ ./
# See example makefile for build env vars
RUN make <project-name>
CMD ["./<project-name>"]
# Use barebones base image to restrict container size
FROM debian:stretch-slim AS runner
ENV DB2DIR="$HOME/sqllib"
RUN apt-get update \
&& apt-get install -y curl libxml2 \
&& curl -o "db2clidriver.tgz" "https://public.dhe.ibm.com/ibmdl/export/pub/software/data/db2/drivers/odbc_cli/linuxx64_odbc_cli.tar.gz" \
&& tar xf "db2clidriver.tgz" \
&& mv "clidriver" "${DB2DIR}" \
&& rm -r *.tgz \
&& ln -sv ${DB2DIR}/lib/* /usr/lib/ \
&& ln -sv ${DB2DIR}/include/* /usr/include/ \
&& mkdir -p /tmp/app/certs \
&& curl \
-o "/tmp/app/certs/āāāāā" "āāāāāāāāāāāāāāāāāā" \
-o "/tmp/app/certs/āāāāā" "āāāāāāāāāāāāāāāāāā" \
&& apt-get purge -y curl \
&& apt-get autoremove -y
# Load binary from builder
COPY --from=builder /tmp/app/<project-name> "/tmp/app/<project-name>"
# Only copy necessary files from builder ex: config directories & SQL files
COPY --from=builder /tmp/app/config "/tmp/app/config"
COPY --from=builder /tmp/app/sql "/tmp/app/sql"
CMD ["/tmp/app/<project-name>"]
Example Makefile
DB2DIR ?= $(HOME)/sqllib
DYLD_LIBRARY_PATH ?= $(DB2DIR)/lib
GO_CFLAGS ?= -I$(DB2DIR)/include
GO_LDFLAGS ?= -L$(DB2DIR)/lib
coverage_data = coverage/data.out
coverage_webpage = coverage/index.html
build_env_vars = CGO_CFLAGS="$(GO_CFLAGS)" CGO_LDFLAGS="$(GO_LDFLAGS)" DYLD_LIBRARY_PATH="$(DYLD_LIBRARY_PATH)"
.PHONY: <project-name> fmt test test-cov test-html vet %.go
<project-name>: %.go
$(build_env_vars) go build ./cmd/<project-name>/
test:
$(build_env_vars) go test ./...
test-cov:
$(build_env_vars) go test ./... -cover
$(coverage_data): %.go
$(build_env_vars) go test ./... -coverprofile=$@
$(coverage_webpage): $(coverage_data)
go tool cover -html=$< -o $@
test-html: $(coverage_webpage)
I'm getting some unhelpful errors when trying to connect to a DB2 Database with an SSL connection. My connection string structure is as follows:
And the error I'm seeing is:
I'm wondering if there is any additional configuration steps required, or if you have gotten SSL to work? Maybe my connection string parameters are incorrect? I have tried using an absolute path as well as a relative path for the
SSLServerCertificate
value, but no luck either way. I have also been able to connect to the DB2 Database with SSL using Node.js and theibm_db
package with the same connection string so I'm confused why it isn't working with this package. Any insight would be greatly appreciated.