I wish to upgrade my Mongo database from version 4 to 5. I use ssl configuration with Mongo to ensure communication with the database is encrypted. To backup the database I use mongodump.
In Mongo 4.2 mongodump was rewritten in Go lang, causing it to import a common Go bug around ssl certificate handling. Specifically PEM files with intermediate certificates aren't fully loaded. The bug does not impact Mongo server or client itself, any version, or any other apps. Only mongodump is impacted.
The bug is described here: https://jira.mongodb.org/browse/TOOLS-2598
Since tool's SSL/TLS code is copied from Go driver, the current implementation only parses the last certificate inside the pem file
This is a discrepancy with Mongoshell behavior, which only loads the first certificate inside the pem file.
In this related exchange: https://jira.mongodb.org/browse/TOOLS-2996 I fail to see the resolution. I have tried every permutation of keys and certificates in the arguments passed to mongodump.
I've been looking at the source code for mongodump and specifically the SSL loading code.
To aid in go development, I've created this dockerfile to instantly provide a working environment for building this code but I am unfamiliar with go as a language.
FROM centos:8
RUN yum -y update
RUN yum -y install git-core vim-enhanced golang krb5-devel krb5-libs snappy
RUN groupadd -r app -g 1000 && \
useradd -r -g app -u 1000 app -d /app && \
mkdir -p /app && \
chown -R app:app /app
USER 1000
WORKDIR /app
ENTRYPOINT /bin/bash
How plausible is it to fix these PEM loading bugs in this code base? My Mongo estate has many clients so rotating the certificates to solve this issue involves a high degree of planning and downtime. Patching mongodump to accept the existing certs feels like an acceptable medium term trade off.
Is anyone able to help me write the appropriate patch, perhaps there's standard ssl code that go developers use now? Does anyone have any ideas on how I can move this forward please? (Patches would be ideal!)
I apologise in advance that I have no reproducible test case here due to complexities of how the certificates I want to test were created.
This patch I've written (based on a rejected git pull request for GODRIVER-1753) resolves the issues.
https://jira.mongodb.org/browse/GODRIVER-1753
https://github.com/mongodb/mongo-go-driver/pull/521/files
It also fixes an asn1 trailing data issue too.
diff --git a/README.md b/README.md
index 20f3ffe8..4b3bed1a 100644
--- a/README.md
+++ b/README.md
## -1,3 +1,13 ##
+
+** PATCHED MONGO-TOOLS **
+
+In Mongo 4.2, the mongo team rewrote many of their core tools in Go.
+
+This introduced a bug with the way that PEM files are handled.
+Faced with downtime and complexity in a large Mongo estate, this
+patched version of mongo resolves the issues.
+
+
MongoDB Tools
===================================
diff --git a/common/db/db.go b/common/db/db.go
index 3e78abab..9290bb31 100644
--- a/common/db/db.go
+++ b/common/db/db.go
## -186,9 +186,13 ## func addClientCertFromBytes(cfg *tls.Config, data []byte, keyPasswd string) (str
}
if currentBlock.Type == "CERTIFICATE" {
- certBlock = data[start : len(data)-len(remaining)]
- certDecodedBlock = currentBlock.Bytes
- start += len(certBlock)
+ tempCertBlock := data[start : len(data)-len(remaining)]
+ certBlock = append(certBlock, tempCertBlock...) //To handle usecase where multiple certs are present
+
+ tempCertEncodedBlock := currentBlock.Bytes
+ certDecodedBlock = append(certDecodedBlock, tempCertEncodedBlock...)
+
+ start += len(tempCertBlock)
} else if strings.HasSuffix(currentBlock.Type, "PRIVATE KEY") {
isEncrypted := x509.IsEncryptedPEMBlock(currentBlock) || strings.Contains(currentBlock.Type, "ENCRYPTED PRIVATE KEY")
if isEncrypted {
## -244,12 +248,17 ## func addClientCertFromBytes(cfg *tls.Config, data []byte, keyPasswd string) (str
// The documentation for the tls.X509KeyPair indicates that the Leaf certificate is not
// retained.
- crt, err := x509.ParseCertificate(certDecodedBlock)
- if err != nil {
- return "", err
- }
-
- return crt.Subject.String(), nil
+ l := len(certDecodedBlock)
+ for {
+ crt, err := x509.ParseCertificate(certDecodedBlock[0:l])
+ if err == nil {
+ return crt.Subject.String(), nil
+ }
+ l = l - 1
+ if l == 0 {
+ return "", err
+ }
+ }
}
// create a username for x509 authentication from an x509 certificate subject.
diff --git a/vendor/go.mongodb.org/mongo-driver/mongo/options/clientoptions.go b/vendor/go.mongodb.org/mongo-driver/mongo/options/clientoptions.go
index d66114fa..2f3c6554 100644
--- a/vendor/go.mongodb.org/mongo-driver/mongo/options/clientoptions.go
+++ b/vendor/go.mongodb.org/mongo-driver/mongo/options/clientoptions.go
## -941,9 +941,13 ## func addClientCertFromBytes(cfg *tls.Config, data []byte, keyPasswd string) (str
}
if currentBlock.Type == "CERTIFICATE" {
- certBlock = data[start : len(data)-len(remaining)]
- certDecodedBlock = currentBlock.Bytes
- start += len(certBlock)
+ tempCertBlock := data[start : len(data)-len(remaining)]
+ certBlock = append(certBlock, tempCertBlock...) //To handle usecase where multiple certs are present
+
+ tempCertEncodedBlock := currentBlock.Bytes
+ certDecodedBlock = append(certDecodedBlock, tempCertEncodedBlock...)
+
+ start += len(tempCertBlock)
} else if strings.HasSuffix(currentBlock.Type, "PRIVATE KEY") {
isEncrypted := x509.IsEncryptedPEMBlock(currentBlock) || strings.Contains(currentBlock.Type, "ENCRYPTED PRIVATE KEY")
if isEncrypted {
## -997,12 +1001,18 ## func addClientCertFromBytes(cfg *tls.Config, data []byte, keyPasswd string) (str
// The documentation for the tls.X509KeyPair indicates that the Leaf certificate is not
// retained.
- crt, err := x509.ParseCertificate(certDecodedBlock)
- if err != nil {
- return "", err
- }
+ l := len(certDecodedBlock)
+ for {
+ crt, err := x509.ParseCertificate(certDecodedBlock[0:l])
+ if err == nil {
+ return crt.Subject.String(), nil
+ }
+ l = l - 1
+ if l == 0 {
+ return "", err
+ }
+ }
- return x509CertSubject(crt), nil
}
func stringSliceContains(source []string, target string) bool {
Related
I'm working on an early iteration of an operator, which I've scaffolded using operator-sdk. I've tried my best to follow the examples from the Operator SDK Golang Tutorial and the Kubebuilder book. I've found that I can deploy and run my operator to a local cluster, but I'm unable to run the test suite. My tests always produce a panic: runtime error: invalid memory address or nil pointer dereference, which I've tracked down to the fact that the Scheme is always Nil. But so far, I haven't been able to figure out why that is.
In theory, I could skip the tests and just test out the operator in my local cluster, but that's going to be really brittle long-term. I'd like to be able to do TDD, and more importantly I'd like to have a test suite to go along with the operator to help maintain quality once it's in maintenance mode.
Here's my suite_test.go, which I'm modified as little as possible from the scaffolded version (the changes I have made are from the Kubebuilder Book):
package controllers
import (
"path/filepath"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
mybatch "mycorp.com/mybatch-operator/api/v1alpha1"
// +kubebuilder:scaffold:imports
)
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
var cfg *rest.Config
var k8sClient client.Client
var testEnv *envtest.Environment
func TestAPIs(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecsWithDefaultAndCustomReporters(t,
"Controller Suite",
[]Reporter{printer.NewlineReporter{}})
}
var _ = BeforeSuite(func(done Done) {
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
}
cfg, err := testEnv.Start()
Expect(err).NotTo(HaveOccurred())
Expect(cfg).NotTo(BeNil())
err = mybatch.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
// +kubebuilder:scaffold:scheme
k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
Scheme: scheme.Scheme,
})
Expect(err).ToNot(HaveOccurred())
err = (&MyBatchReconciler{
Client: k8sManager.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("MyBatch"),
}).SetupWithManager(k8sManager)
Expect(err).ToNot(HaveOccurred())
go func() {
err = k8sManager.Start(ctrl.SetupSignalHandler())
Expect(err).ToNot(HaveOccurred())
}()
k8sClient = k8sManager.GetClient()
Expect(k8sClient).ToNot(BeNil())
close(done)
}, 60)
var _ = AfterSuite(func() {
By("tearing down the test environment")
err := testEnv.Stop()
Expect(err).NotTo(HaveOccurred())
})
Here's the test block that causes it to fail. I have a second Describe block (not shown here), which tests some of the business logic outside of the Reconcile function, and that works fine.
package controllers
import (
"context"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"github.com/jarcoal/httpmock"
mybatch "mycorp.com/mybatch-operator/api/v1alpha1"
)
var _ = Describe("BatchController", func() {
Describe("Reconcile", func() {
// Define utility constants for object names and testing timeouts/durations and intervals.
const (
BatchName = "test-batch"
BatchNamespace = "default"
BatchImage = "mycorp/mockserver:latest"
timeout = time.Second * 10
duration = time.Second * 10
interval = time.Millisecond * 250
)
Context("When deploying MyBatch", func() {
It("Should create a new Batch instance", func() {
ctx := context.Background()
// Define stub Batch
testCR := &mybatch.MyBatch{
TypeMeta: metav1.TypeMeta{
APIVersion: "mybatch.mycorp.com/v1alpha1",
Kind: "MyBatch",
},
ObjectMeta: metav1.ObjectMeta{
Name: BatchName,
Namespace: BatchNamespace,
},
Spec: mybatch.MyBatchSpec{
Replicas: 1,
StatusCheck: mybatch.StatusCheck{
Url: "http://mycorp.com",
Endpoint: "/rest/jobs/jobexecutions/active",
PollSeconds: 20,
},
Image: BatchImage,
PodSpec: corev1.PodSpec{
// For simplicity, we only fill out the required fields.
Containers: []corev1.Container{
{
Name: "test-container",
Image: BatchImage,
},
},
RestartPolicy: corev1.RestartPolicyAlways,
},
},
}
Expect(k8sClient.Create(ctx, testCR)).Should(Succeed())
lookupKey := types.NamespacedName{Name: BatchName, Namespace: BatchNamespace}
createdBatch := &mybatch.MyBatch{}
// We'll need to retry getting this newly created Batch, given that creation may not immediately happen.
Eventually(func() bool {
err := k8sClient.Get(ctx, lookupKey, createdBatch)
if err != nil {
return false
}
return true
}, timeout, interval).Should(BeTrue())
// Check the container name
Expect(createdBatch.Spec.PodSpec.Containers[0].Name).Should(Equal(BatchName))
})
})
})
})
Is there something I'm missing here that's preventing Scheme from being properly initialized? I have to admit that I don't really understand much of the code around the Scheme. I'm happy to show additional code if it will help.
I have a golang application with mongodb connected using mongo-driver.
Everything is working fine if I run the main file as go run main.go
It is able to connect to database as expected.
However if I dockerize the application it is unable communicate to the database whenever I hit the api that interacts with the database it exits with message
the Database field must be set on Operation
Dockerfile
ARG ROOT="/go/src/bitbucket.org/myteam/myapp"
ARG BIN="/go/bin"
FROM golang:1.13 as build
ARG ROOT
ARG BIN
ENV GOBIN $BIN
ENV GOPATH /go
RUN apt-get update
RUN apt-get install go-dep -y
RUN mkdir -p $ROOT
RUN mkdir -p $BIN
WORKDIR /go
COPY .env .
WORKDIR $ROOT
COPY src/bitbucket.org/myteam/myapp .
RUN ["chmod", "+x", "main.go"]
RUN dep ensure
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo
FROM alpine:3.10
ARG ROOT
RUN apk --no-cache add ca-certificates
RUN mkdir -p /go/bin
WORKDIR /go/bin
RUN mkdir -p /go/bin
WORKDIR /go/bin
COPY --from=build $ROOT/myapp myapp
EXPOSE 3000
CMD ["./myapp"]
Connection File
config := conf.GetInstance()
ctx, _ := context.WithTimeout(context.Background(), 1*time.Second)
var clientOptions *options.ClientOptions
if config.MONGODB_USERNAME != "" && config.MONGODB_PASSWORD != "" {
if config.Env == "dev" {
clientOptions = options.Client().ApplyURI("mongodb://" + config.MONGODB_HOST + ":" + config.MONGODB_PORT).SetAuth(options.Credential{
AuthSource: config.MONGODB_NAME, Username: config.MONGODB_USERNAME, Password: config.MONGODB_PASSWORD,
})
} else {
mongoURI := fmt.Sprintf("mongodb+srv://%s:%s#%s", config.MONGODB_USERNAME, config.MONGODB_PASSWORD, config.MONGODB_HOST)
clientOptions = options.Client().ApplyURI(mongoURI)
}
} else {
clientOptions = options.Client().ApplyURI("mongodb://" + config.MONGODB_HOST + ":" + config.MONGODB_PORT)
}
client, err := mongo.Connect(ctx, clientOptions)
if err != nil {
logrus.Fatal("Error connecting to database", err)
}
db := client.Database(config.MONGODB_NAME)
Migrate(db, ctx)
logrus.Info("Database connection successful")
return db
This error happens when the mongodb database name is not available for the connection.
Make sure your config.MONGODB_* variables are available to set the connection string for mongodb.
I've got a multi document yaml file, how can I provision a Kubernetes cluster from this?
Using kops create -f only seems to import the cluster definition, and does not import the instance groups etc which are defined in additional 'documents' with the yaml:
...
masters: private
nodes: private
---
apiVersion: kops/v1alpha2
kind: InstanceGroup
...
According to kops source code, multiple section in one file is supported.
Perhaps you need to check your file for mistakes.
Here is the part responsible for parsing file and creating resources. To make code shorter, I've deleted all the error checks.
For investigation, please see original file:
func RunCreate(f *util.Factory, out io.Writer, c *CreateOptions) error {
clientset, err := f.Clientset()
// Codecs provides access to encoding and decoding for the scheme
codecs := kopscodecs.Codecs //serializer.NewCodecFactory(scheme)
codec := codecs.UniversalDecoder(kopsapi.SchemeGroupVersion)
var clusterName = ""
//var cSpec = false
var sb bytes.Buffer
fmt.Fprintf(&sb, "\n")
for _, f := range c.Filenames {
contents, err := vfs.Context.ReadFile(f)
// TODO: this does not support a JSON array
sections := bytes.Split(contents, []byte("\n---\n"))
for _, section := range sections {
defaults := &schema.GroupVersionKind{
Group: v1alpha1.SchemeGroupVersion.Group,
Version: v1alpha1.SchemeGroupVersion.Version,
}
o, gvk, err := codec.Decode(section, defaults, nil)
switch v := o.(type) {
case *kopsapi.Cluster:
// Adding a PerformAssignments() call here as the user might be trying to use
// the new `-f` feature, with an old cluster definition.
err = cloudup.PerformAssignments(v)
_, err = clientset.CreateCluster(v)
case *kopsapi.InstanceGroup:
clusterName = v.ObjectMeta.Labels[kopsapi.LabelClusterName]
cluster, err := clientset.GetCluster(clusterName)
_, err = clientset.InstanceGroupsFor(cluster).Create(v)
case *kopsapi.SSHCredential:
clusterName = v.ObjectMeta.Labels[kopsapi.LabelClusterName]
cluster, err := clientset.GetCluster(clusterName)
sshCredentialStore, err := clientset.SSHCredentialStore(cluster)
sshKeyArr := []byte(v.Spec.PublicKey)
err = sshCredentialStore.AddSSHPublicKey("admin", sshKeyArr)
default:
glog.V(2).Infof("Type of object was %T", v)
return fmt.Errorf("Unhandled kind %q in %s", gvk, f)
}
}
}
{
// If there is a value in this sb, this should mean that we have something to deploy
// so let's advise the user how to engage the cloud provider and deploy
if sb.String() != "" {
fmt.Fprintf(&sb, "\n")
fmt.Fprintf(&sb, "To deploy these resources, run: kops update cluster %s --yes\n", clusterName)
fmt.Fprintf(&sb, "\n")
}
_, err := out.Write(sb.Bytes())
}
return nil
}
This may not have been possible in the past, but it's possible now.
Here's how I'm currently IaC'ing my kops cluster.
I have 2 files:
devkube.mycompany.com.cluster.kops.yaml
instancegroups.kops.yaml
The 2nd file is a multi-document yaml file like you're requesting.
(with multiple instance.yaml's inside of it separated by ---).
Note: I've successfully been able to combine both of these .yaml files into a single multi-document .yaml file and have it work fine, I just do it this way so I can reuse the instancegroups.yaml on multiple clusters / keep the git repo dry.
I generated the files by using:
Bash# kops get cluster --name devkube.mycompany.com -o yaml > devkube.mycompany.com.cluster.kops.yaml
Bash# kops get ig --name devkube.mycompany.com -o yaml > instancegroups.kops.yaml
Here's how I use these files to provision a kops cluster from git:
export AWS_PROFILE=devkube-kops
export KOPS_STATE_STORE=s3://kops-state-devkube.mycompany.com
clustername="devkube.mycompany.com"
kops replace --name $clustername -f ../kops/qak8s.vibrenthealth.com.cluster.yaml --force
kops replace --name $clustername -f ../kops/instancegroups.yaml --force
kops create secret --name $clustername sshpublickey admin -i ~/.ssh/id_rsa.pub #so the person who provisions the cluster can ssh into the nodes
kops update cluster --name $clustername --yes
I've recently re-installed Mongodb switching from the enterprise to the community version. Nevertheless, when I start mongo, this is the prompt format I get:
MongoDB Enterprise >
How can I change it to the standard prompt version? (i.e., > if I am not wrong)
You can change the prompt from within a shell session by setting the prompt variable. For example, issuing the following command within a Mongo shell ...
var prompt="this_prompt >"
... will result in the shell's prompt changing to:
this_prompt >
You can change the default prompt for all future sessions by updating your .mongorc.js (you'll find this in your $HOME directory and if it does not exist then just create it). The following addition to your .mongorc.js ...
var prompt=function() {
return ISODate().toLocaleTimeString() + " > ";
}
... will result in this prompt:
:~/dev/tools/mongodb/mongodb-osx-x86_64-3.4.7/bin$ ./mongo
MongoDB shell version v3.4.7
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.4.7
16:09:43 >
16:09:57 >
Or, to get this prompt: > just add the following to your .mongorc.js:
var prompt=">"
There remains the sub text to your question, namely; where is the existing MongoDB Enterprise > prompt coming from?. It's hard to say but ...
Perhaps you have a Global mongorc.js; have a look in /etc/mongorc.js if you are on a *nix system or %ProgramData%\MongoDB if you are on Windows
Mongo behavour when finding a rc file is also subject to environment variables, more details in the docs.
When you search the internet for this topic then often you find the function below. If you like to include the existing prompt then use function defaultPrompt(), e.g.
prompt = function() {
if (typeof db == 'undefined')
return '(nodb)> ';
// Check the last db operation
try {
db.runCommand( {getLastError:1} );
} catch (e) {
print(e);
}
var user = db.runCommand({connectionStatus : 1}).authInfo.authenticatedUsers[0]
if (user) {
return user.user + "#" + db + " " + defaultPrompt();
} else {
return "(anonymous)#" + db + " " + defaultPrompt();
//return db + " " + defaultPrompt(); // if you prefer
}
}
Prompts will be like this:
admin#mip mongos>
mip shard001:ARBITER>
admin#mip configRepSet:PRIMARY>
I am creating a project in Go and I am using both "github.com/docker/docker/client" and "github.com/docker/docker/api/types", but when I try and create a container I get the following error:
ERROR: 2016/10/03 22:39:26 containers.go:84: error during connect: Post https://%2Fvar%2Frun%2Fdocker.sock/v1.23/containers/create: http: server gave HTTP response to HTTPS client
I can't understand why this is happening and it only happened after using the new golang docker engine(the old "github.com/docker/engine-api" is now deprecated).
The code isn't anything complicated, so I wonder if I am missing something:
resp, err := cli.Pcli.ContainerCreate(context.Background(), initConfig(), nil, nil, "")
if err != nil {
return err
}
And the initConfig that is called does the following:
func initConfig() (config *container.Config) {
mount := map[string]struct{}{"/root/host": {}}
return &container.Config{Image: "leadis_image", Volumes: mount, Cmd: strslice.StrSlice{"/root/server.py"}, AttachStdout: true}}
Also here is my dockerfile
FROM debian
MAINTAINER Leadis Journey
LABEL Description="This docker image is used to compile and execute user's program."
LABEL Version="0.1"
VOLUME /root/host/
RUN apt-get update && yes | apt-get upgrade
RUN yes | apt-get install gcc g++ python3 make
COPY container.py /root/server.py
EDIT
Just tried to test it with a simpler program
package main
import (
"fmt"
"os"
"io/ioutil"
"github.com/docker/docker/client"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/strslice"
"golang.org/x/net/context"
)
func initConfig() (config *container.Config) {
mount := map[string]struct{}{"/root/host": {}}
return &container.Config{Image: "leadis_image", Volumes: mount, Cmd: strslice.StrSlice{"/root/server.py"}, AttachStdout: true}
}
func main() {
client, _ := client.NewEnvClient()
cwd, _ := os.Getwd()
ctx, err := os.Open(cwd+"/Dockerfile.tar.gz")
if err != nil {
fmt.Println(err)
return
}
build, err := client.ImageBuild(context.Background(), ctx, types.ImageBuildOptions{Tags: []string{"leadis_image"}, Context: ctx, SuppressOutput: false})
if err != nil {
fmt.Println(err)
return
}
b, _ := ioutil.ReadAll(build.Body)
fmt.Println(string(b))
_, err = client.ContainerCreate(context.Background(), initConfig(), nil, nil, "")
if err != nil {
fmt.Println(err)
}
}
Same dockerfile, but I still get the same error:
error during connect: Post
https://%2Fvar%2Frun%2Fdocker.sock/v1.23/containers/create: http:
server gave HTTP response to HTTPS client
client.NewEnvClient()
Last time I tried, this API expects environment variables like DOCKER_HOST in a different syntax from than the normal docker client.
From the client.go:
// NewEnvClient initializes a new API client based on environment variables.
// Use DOCKER_HOST to set the url to the docker server.
// Use DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest.
// Use DOCKER_CERT_PATH to load the TLS certificates from.
// Use DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default.
To use this, you need DOCKER_HOST to be set/exported in one of these formats:
unix:///var/run/docker.sock
http://localhost:2375
https://localhost:2376