Criptografia no nível do campo do lado do cliente (CSFLE) no MongoDB com Golang
Avalie esse Tutorial
Uma das muitas coisas boas sobre o MongoDB é o quão seguro você pode tornar seus dados nele. Além das regras baseadas em rede e usuário, você tem criptografia de seus dados em repouso, criptografia pela conexão e, agora, recentemente, criptografia do lado do cliente conhecida como criptografia de nível de campo do lado do cliente (CSFLE).
Então, o que é exatamente a criptografia no nível do campo do lado do cliente (CSFLE) e como você a usa?
Com a criptografia em nível de campo, você pode optar por criptografar determinados campos em um documento, do lado do cliente, enquanto deixa outros campos como texto sem formatação. Isso é particularmente útil porque, ao visualizar um documento CSFLE com a CLI, oCompassou diretamente no Altas, os campos criptografados não serão legíveis por humanos. Quando não forem legíveis por humanos, se os documentos cairem em mãos erradas, esses campos serão inúteis para o usuário mal-intencionado. No entanto, ao usar os drivers de linguagem MongoDB e as mesmas chaves de criptografia, esses campos podem ser descriptografados e consultados no aplicativo.
Neste tutorial de início rápido, vamos ver como usar a criptografia em nível de campo do MongoDB com a linguagem de programação Go (Golang). Em particular, vamos explorar a criptografia automática em vez da criptografia manual.
Existem alguns requisitos que devem ser atendidos antes de tentar usar o CSFLE com o driver Go.
- MongoDB Atlas 4.2+
- Driver Go do MongoDB 1.2+
Este tutorial se concentrará na criptografia automática. Embora este tutorial use o MongoDB Atlas, você precisará usar a versão 4.2 ou mais recente para MongoDB Atlas ou MongoDB Enterprise Edition. Você não poderá usar criptografia automática em nível de campo com o MongoDB Community Edition.
A suposição é que você esteja familiarizado com o desenvolvimento de aplicativos Go que usam MongoDB. Se quiser se atualizar, dê uma olhada na série de início rápido que publiquei sobre o tópico.
Para usar a criptografia em nível de campo, você precisará de um pouco mais do que apenas ter uma versão apropriada do MongoDB e do driver MongoDB Go. Vamos precisar delibmongocrypt, que é uma biblioteca complementar para criptografia nos drivers do MongoDB, e mongocryptd, que é um binário para analisar regras de criptografia automática com base no formato JSON estendido.
Devido aos requisitosdo libmongocrypt e do mongocryptd, vale a pena revisar como instalá-los e configurá-los. Exploraremos a instalação no macOS, mas consulte a documentação de libmongocrypt e mongocryptd para seu sistema operacional específico.
Existem algumas soluções para instalar a bibliotecalibmongocrypt no macOS, sendo a mais fácil com o Homebrew. Se você tiver o Homebrew instalado, poderá instalar alibmongocrypt com o seguinte comando:
1 brew install mongodb/brew/libmongocrypt
Só assim, o driver MongoDB Go será capaz de lidar com a criptografia. Mais explicações sobre as instruções podem ser encontradas na documentação.
Como queremos fazer a criptografia automática com o driver usando um JSON schema estendido, precisamos demongocryptd, um binário fornecido com o MongoDB Enterprise Edition. O bináriomongocryptd precisa existir no computador ou servidor onde o aplicativo Go pretende ser executado. Não é uma dependência de desenvolvimento como libmongocrypt, mas uma dependência de tempo de execução.
Consulte a documentação sobre como obter o bináriomongocryptd, pois cada sistema operacional tem etapas diferentes.
Para macOS, você deve baixar o MongoDB Enterprise Edition no Centro de Download do MongoDB . Você pode consultar as instruções de instalação da Enterprise Edition para instalar o macOS, mas a essência da instalação envolve extrair o arquivo TAR e movê-los para o diretório apropriado .
A essa altura, todos os componentes apropriados para a criptografia em nível de campo devem estar instalados ou disponíveis.
Antes de começarmos a criptografar e descriptografar campos em nossos documentos, precisamos estabelecer chaves para fazer a maior parte do trabalho. Isso significa definir a localização do nosso cofre de chaves no MongoDB e no Sistema de Gerenciamento de Chaves (KMS) que desejamos usar para descriptografar as chaves de criptografia de dados.
O cofre de chaves é uma collection que criaremos no MongoDB para armazenar chaves criptografadas para os campos de nossos documentos. A chave primária dentro do KMS descriptografará as chaves dentro do cofre de chaves.
Para este tutorial específico, vamos usar um provedor de chaves locais para o nosso KMS. vale a pena procurar algo como AWS KMS ou semelhante, algo que exploraremos em um tutorial futuro, como alternativa a um provedor de chave local.
No seu computador, crie um novo projeto Go com o seguinte arquivo principal.go:
1 package main 2 3 import ( 4 "context" 5 "crypto/rand" 6 "fmt" 7 "io/ioutil" 8 "log" 9 "os" 10 11 "go.mongodb.org/mongo-driver/bson" 12 "go.mongodb.org/mongo-driver/mongo" 13 "go.mongodb.org/mongo-driver/mongo/options" 14 ) 15 16 var ( 17 ctx = context.Background() 18 kmsProviders map[string]map[string]interface{} 19 schemaMap bson.M 20 ) 21 22 func createDataKey() {} 23 func createEncryptedClient() *mongo.Client {} 24 func readSchemaFromFile(file string) bson.M {} 25 26 func main() {}
Você precisará instalar o driver MongoDB Go para continuar. Para saber como fazer isso, reserve um momento para conferir meu tutorial anterior intitulado Quick Start: Golang & MongoDB - Starting and Setup.
No código acima, temos algumas variáveis definidas, bem como algumas funções. Vamos nos concentrar na variável
kmsProviders
e na funçãocreateDataKey
para esta parte específica do tutorial.Dê uma olhada na seguinte função
createDataKey
:1 func createDataKey() { 2 kvClient, err := mongo.Connect(ctx, options.Client().ApplyURI(os.Getenv("ATLAS_URI"))) 3 if err != nil { 4 log.Fatal(err) 5 } 6 clientEncryptionOpts := options.ClientEncryption().SetKeyVaultNamespace("keyvault.datakeys").SetKmsProviders(kmsProviders) 7 clientEncryption, err := mongo.NewClientEncryption(kvClient, clientEncryptionOpts) 8 if err != nil { 9 log.Fatal(err) 10 } 11 defer clientEncryption.Close(ctx) 12 _, err = clientEncryption.CreateDataKey(ctx, "local", options.DataKey().SetKeyAltNames([]string{"example"})) 13 if err != nil { 14 log.Fatal(err) 15 } 16 }
Na função
createDataKey
acima, estamos nos conectando pela primeira vez ao MongoDB. A connection string do MongoDB é definida pela variável de ambiente ATLAS_URI
no código acima. Embora você possa codificar essa connection string ou armazená-la em um arquivo de configuração, por motivos de segurança, faz muito sentido usar variáveis de ambiente.Se a conexão for bem-sucedida, precisaremos definir o espaço de nome do cofre de chaves e o provedor KMS como parte das opções de configuração de criptografia. O namespace é composto pelo nome do banco de dados seguido pelo nome da collection. É aqui que as informações da chave serão armazenadas. O mapa
kmsProviders
, que será definido posteriormente, terá informações de chaves locais.A execução da função
CreateDataKey
criará as informações-chave no MongoDB como um documento.Optamos por especificar um nome de chave alternativo de
example
para que não precisemos nos referir à chave de dados pelo _id
ao usá-la em nossos documentos. Em vez disso, poderemos usar o nome alternativo exclusivo que pode seguir uma convenção de nomenclatura especial. É importante observar que o nome alternativo da chave só é útil quando se usa o AEAD_AES_256_CBC_HMAC_SHA_512-Random
, algo que exploraremos mais adiante neste tutorial.Para usar a função
createDataKey
, podemos fazer algumas modificações na funçãomain
:1 func main() { 2 localKey := make([]byte, 96) 3 if _, err := rand.Read(localKey); err != nil { 4 log.Fatal(err) 5 } 6 kmsProviders = map[string]map[string]interface{}{ 7 "local": { 8 "key": localKey, 9 }, 10 } 11 createDataKey() 12 }
No código acima, estamos gerando uma chave aleatória. Esta chave aleatória é adicionada ao mapa
kmsProviders
que estamos usando dentro da funçãocreateDataKey
.Não é seguro ter sua chave local armazenada dentro do aplicativo ou no mesmo servidor. Na produção, considere usar o AWS KMS ou acessar sua chave local por meio de uma solicitação separada antes de adicioná-la ao provedor de chave local.
Se você executou o código até agora, acabaria com um banco de dados
keyvault
e uma coleçãodatakeys
que possui um documento de uma chave com um nome alternativo. Esse documento pareceria algo assim:1 { 2 "_id": UUID("27a51d69-809f-4cb9-ae15-d63f7eab1585"), 3 "keyAltNames": [ 4 "example" 5 ], 6 "keyMaterial": Binary("oJ6lEzjIEskHFxz7zXqddCgl64EcP1A7E/r9zT+OL19/ZXVwDnEjGYMvx+BgcnzJZqkXTFTgJeaRYO/fWk5bEcYkuvXhKqpMq2ZO", 0), 7 "creationDate": 2020-11-05T23:32:26.466+00:00, 8 "updateDate": 2020-11-05T23:32:26.466+00:00, 9 "status": 0, 10 "masterKey": { 11 "provider": "local" 12 } 13 }
Há algumas coisas importantes a serem observadas em nosso código até agora:
- O
localKey
é aleatório e não persiste além do tempo de execução, o que resultará em incompatibilidades de chave em execuções consecutivas do aplicativo. Especifique uma chave não aleatória ou armazene-a em algum lugar após a geração. - Estamos usando um provedor de chaves locais com uma chave que existe localmente. Isso não é recomendado em um cenário de produção devido a preocupações de segurança. Em vez disso, use um fornecedor como Amazon Web Services KMS ou armazene a chave externamente.
- O
createDataKey
só deve ser executado quando uma chave específica precisar ser criada, não sempre que o aplicativo for executado. - Não há nenhuma convenção de nomenclatura estrita para o cofre de chaves e as chaves que residem nele. Dê um nome ao seu banco de dados e à collection da forma que fizer sentido para você.
Depois de executarmos nosso aplicativo pela primeira vez, provavelmente vamos querer comentar a linha
createDataKey
na função main
.Com a chave de dados criada, estamos em um ponto no tempo em que precisamos descobrir quais campos devem ser criptografados em um documento e quais campos devem ser deixados como texto sem formatação. A maneira mais fácil de fazer isso é com um mapa de esquema.
Um mapa de esquema para criptografia é JSON estendido e pode ser adicionado diretamente ao código-fonte Go ou carregado de um arquivo externo. Do ponto de vista da manutenção, o carregamento de um arquivo externo é mais fácil de manter.
Dê uma olhada no seguinte mapa de esquema para criptografia:
1 { 2 "fle-example.people": { 3 "encryptMetadata": { 4 "keyId": "/keyAltName" 5 }, 6 "properties": { 7 "ssn": { 8 "encrypt": { 9 "bsonType": "string", 10 "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" 11 } 12 } 13 }, 14 "bsonType": "object" 15 } 16 }
Vamos presumir que o JSON acima exista em um arquivoschema.json que seja relativo aos nossos arquivos Go ou binário. No JSON acima, estamos dizendo que o mapa se aplica à coleção
people
dentro do banco de dadosfle-example
.O campo
keyId
dentro do objeto encryptMetadata
diz que os documentos dentro da coleçãopeople
devem ter um campo de string chamado keyAltName
. O valor desse campo refletirá o nome da chave alternativa que definimos ao criar a chave de dados. Observe o /
que prefixa o valor. Isso não é um erro. É um requisito para esse valor específico, pois se trata de um ponteiro.O campo
properties
lista os campos do nosso documento e, neste exemplo, lista os campos que devem ser criptografados juntamente com o algoritmo de criptografia a ser usado. Em nosso exemplo, somente o campossn
será criptografado, enquanto todos os outros campos permanecerão como texto simples.Existem dois algoritmos atualmente suportados:
- AEAD_AES_256_CBC_HMAC_SHA_512-Random
- AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic
Em suma, o algoritmo
AEAD_AES_256_CBC_HMAC_SHA_512-Random
é melhor usado em campos com baixa cardinalidade ou que não precisam ser usados em um filtro para uma consulta. O algoritmoAEAD_AES_256_CBC_HMAC_SHA_512-Deterministic
deve ser usado para campos com alta cardinalidade ou para campos que precisam ser usados em um filtro.Para saber mais sobre esses algoritmos, acesse a documentação. Exploraremos ambos os algoritmos neste tutorial específico.
Se quisermos, poderíamos alterar o mapa de esquema para o seguinte:
1 { 2 "fle-example.people": { 3 "properties": { 4 "ssn": { 5 "encrypt": { 6 "keyId": "/keyAltName", 7 "bsonType": "string", 8 "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" 9 } 10 } 11 }, 12 "bsonType": "object" 13 } 14 }
A alteração feita no exemplo acima tem a ver com o campo
keyId
. Em vez de declará-lo como parte do encryptMetadata
, nós o declaramos como parte de um campo específico. Isso pode ser útil se você quiser usar chaves diferentes para campos diferentes.Lembre-se de que o ponteiro usado para o
keyId
só funcionará com o algoritmoAEAD_AES_256_CBC_HMAC_SHA_512-Random
. No entanto, você pode usar o ID da chave real para ambos os algoritmos.Com um mapa de esquema para criptografia disponível, vamos carregá-lo no aplicativo Go. Altere a função
readSchemaFromFile
para ter a seguinte aparência:1 func readSchemaFromFile(file string) bson.M { 2 content, err := ioutil.ReadFile(file) 3 if err != nil { 4 log.Fatal(err) 5 } 6 var doc bson.M 7 if err = bson.UnmarshalExtJSON(content, false, &doc); err != nil { 8 log.Fatal(err) 9 } 10 return doc 11 }
No código acima, estamos lendo o arquivo, que será o arquivoschema.json em breve. Se for lido com sucesso, usamos a função
UnmarshalExtJSON
para carregá-lo em um objetobson.M
que seja mais fácil de trabalhar no Go.Nesse ponto, você deve ter o código pronto para criar uma chave de dados e um mapa de esquema definidos para serem usados com a funcionalidade de criptografia automática de cliente que o MongoDB suporta. É hora de reuni-los para realmente criptografar e descriptografar campos.
Vamos começar com a função
createEncryptedClient
em nosso projeto:1 func createEncryptedClient() *mongo.Client { 2 schemaMap = readSchemaFromFile("schema.json") 3 mongocryptdOpts := map[string]interface{}{ 4 "mongodcryptdBypassSpawn": true, 5 } 6 autoEncryptionOpts := options.AutoEncryption(). 7 SetKeyVaultNamespace("keyvault.datakeys"). 8 SetKmsProviders(kmsProviders). 9 SetSchemaMap(schemaMap). 10 SetExtraOptions(mongocryptdOpts) 11 mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(os.Getenv("ATLAS_URI")).SetAutoEncryptionOptions(autoEncryptionOpts)) 12 if err != nil { 13 log.Fatal(err) 14 } 15 return mongoClient 16 }
No código acima, estamos usando a função
readSchemaFromFile
que acabamos de criar para carregar nosso mapa de esquema para criptografia. Em seguida, estamos definindo nossas opções de criptografia automática e estabelecendo uma conexão com o MongoDB. Isso parecerá um tanto familiar com o que fizemos na função createDataKey
. Ao definir as opções de criptografia automática, não apenas especificamos o KMS para nossa chave e cofre, mas também fornecemos o mapa de esquema para criptografia.Você notará que estamos usando
mongocryptdBypassSpawn
como uma opção extra. Estamos fazendo isso para que o cliente não tente iniciar automaticamente o daemonmongocryptd se ele já estiver em execução. Você pode ou não querer usar isso em seu próprio aplicativo.Se a conexão for bem-sucedida, o cliente é retornado.
É hora de revisitar a função
main
dentro do projeto:1 func main() { 2 localKey := make([]byte, 96) 3 if _, err := rand.Read(localKey); err != nil { 4 log.Fatal(err) 5 } 6 kmsProviders = map[string]map[string]interface{}{ 7 "local": { 8 "key": localKey, 9 }, 10 } 11 // createDataKey() 12 client := createEncryptedClient() 13 defer client.Disconnect(ctx) 14 collection := client.Database("fle-example").Collection("people") 15 if _, err := collection.InsertOne(context.TODO(), bson.M{"name": "Nic Raboy", "ssn": "123456", "keyAltName": "example"}); err != nil { 16 log.Fatal(err) 17 } 18 result, err := collection.FindOne(context.TODO(), bson.D{}).DecodeBytes() 19 if err != nil { 20 log.Fatal(err) 21 } 22 fmt.Println(result) 23 }
No código acima, estamos criando nosso provedor de chave local usando uma chave local que foi gerada aleatoriamente. Lembre-se de que essa chave deve corresponder ao que foi usado ao criar a chave de dados, portanto, aleatório pode não ser o melhor a longo prazo. Da mesma forma, uma chave local não deve ser usada na produção por motivos de segurança.
Depois que os provedores de KMS são estabelecidos, a função
createEncryptedClient
é executada. Lembre-se de que essa função específica definirá as opções de criptografia automática e estabelecerá uma conexão com o MongoDB.Para corresponder ao banco de dados e à coleção usados na definição do mapa de esquema, estamos usando
fle-example
como banco de dados e people
como coleção. As operações a seguir, como InsertOne
e FindOne
, podem ser usadas como se a criptografia em nível de campo não existisse. Como temos um campossn
e o campokeyAltName
, o campossn
será criptografado no lado do cliente e salvo no MongoDB. Ao fazer a operação de pesquisa, o campo criptografado será descriptografado.Ao examinar os dados no Atlas, por exemplo, os campos criptografados não serão legíveis por humanos, como visto na captura de tela acima.
Quando a criptografia em nível de campo é incluída no aplicativo Go, uma marcação especial deve ser incluída no processo de compilação ou execução, dependendo da rota escolhida. Você já deve ter mongocryptd e libmongocrypt, então, para construir seu aplicativo Go, você faria o seguinte:
1 go build -tags cse
Se você usar o comando acima para criar seu binário, poderá usá-lo normalmente. No entanto, se você estiver executando o aplicativo sem criar, poderá fazer algo como o seguinte:
1 go run -tags cse main.go
O comando acima executará o aplicativo com a criptografia do lado do cliente ativada.
Se você executou o exemplo até agora, provavelmente notará que, embora possa criptografar e descriptografar campos automaticamente, receberá um erro se tentar usar um filtro que contenha um campo criptografado.
No nosso exemplo até agora, usamos o algoritmo
AEAD_AES_256_CBC_HMAC_SHA_512-Random
em nossos campos criptografados. Para poder filtrar campos criptografados, o AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic
deve ser usado. Mais informações entre as duas opções podem ser encontradas na documentação.Para usar a abordagem determinística, precisamos fazer algumas revisões em nosso projeto. Essas alterações são resultado do fato de que não poderemos usar nomes de chave alternativos em nosso mapa de esquema.
Primeiro, vamos alterar o arquivoschema.json para o seguinte:
1 { 2 "fle-example.people": { 3 "encryptMetadata": { 4 "keyId": [ 5 { 6 "$binary": { 7 "base64": "%s", 8 "subType": "04" 9 } 10 } 11 ] 12 }, 13 "properties": { 14 "ssn": { 15 "encrypt": { 16 "bsonType": "string", 17 "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" 18 } 19 } 20 }, 21 "bsonType": "object" 22 } 23 }
As duas alterações no JSON acima refletem o novo algoritmo e o
keyId
usando o valor_id
real em vez de um alias. Para o campobase64
, observe o uso do espaço reservado%s
. Se você conhece a versão de string64 básica da sua chave, substitua-a e poupe trabalho. Como este tutorial é um exemplo e os dados mudam praticamente toda vez que os executamos, provavelmente queremos trocar esse campo depois que o arquivo for carregado.Começando com a função
createDataKey
, localize a seguinte linha com a chamada da funçãoCreateDataKey
:1 dataKeyId, err := clientEncryption.CreateDataKey(ctx, "local", options.DataKey())
O que não vimos nas partes anteriores deste tutorial é que essa função retorna o
_id
da chave de dados. Provavelmente devemos atualizar nossa funçãocreateDataKey
para retornar primitive.Binary
e, em seguida, retornar essa variáveldataKeyId
.Precisamos mover esse valor
dataKeyId
até que ele chegue ao local onde carregamos nosso arquivo JSON. Estamos trabalhando muito pelos seguintes motivos:- Estamos em um cenário em que não sabemos o
_id
de nossa chave de dados antes do tempo de execução. Se o conhecermos, podemos adicioná-lo ao esquema e pronto. - Criamos nosso código para pular funções.
O mapa de esquema requer um valor base64 para ser usado, portanto, quando passamos
dataKeyId
, precisamos primeiro codificá-lo.Na função
main
, podemos ter algo parecido com isto:1 dataKeyId := createDataKey() 2 client := createEncryptedClient(base64.StdEncoding.EncodeToString(dataKeyId.Data))
Isso significa que o
createEncryptedClient
precisa receber um argumento de string. Atualize o createEncryptedClient
para aceitar uma string e então alterar como estamos lendo nosso arquivo JSON:1 schemaMap = readSchemaFromFile("schema.json", dataKeyIdBase64)
Lembre-se de que estamos apenas passando o valor codificado de base64 pelo pipeline. Ao final disso, na função
readSchemaFromFile
, podemos atualizar nosso código para ficar assim:1 func readSchemaFromFile(file string, dataKeyIdBase64 string) bson.M { 2 content, err := ioutil.ReadFile(file) 3 if err != nil { 4 log.Fatal(err) 5 } 6 content = []byte(fmt.Sprintf(string(content), dataKeyIdBase64)) 7 var doc bson.M 8 if err = bson.UnmarshalExtJSON(content, false, &doc); err != nil { 9 log.Fatal(err) 10 } 11 return doc 12 }
Não apenas estamos recebendo a string64 base, mas também usando uma função
Sprintf
para trocar nosso espaço reservado%s
pelo valor real.Novamente, essas alterações foram baseadas em como projetamos nosso código. No final das contas, só estavamos mudando o
keyId
no mapa de esquema e o algoritmo usado para criptografia. Ao fazer isso, não só podemos descriptografar campos que foram criptografados, mas também filtrar documentos usando campos criptografados.Embora possa parecer que escrevemos muito código, a realidade é que o código era muito mais simples do que os conceitos envolvidos. Para ver melhor o código, você pode encontrá-lo abaixo:
1 package main 2 3 import ( 4 "context" 5 "crypto/rand" 6 "encoding/base64" 7 "fmt" 8 "io/ioutil" 9 "log" 10 "os" 11 12 "go.mongodb.org/mongo-driver/bson" 13 "go.mongodb.org/mongo-driver/bson/primitive" 14 "go.mongodb.org/mongo-driver/mongo" 15 "go.mongodb.org/mongo-driver/mongo/options" 16 ) 17 18 var ( 19 ctx = context.Background() 20 kmsProviders map[string]map[string]interface{} 21 schemaMap bson.M 22 ) 23 24 func createDataKey() primitive.Binary { 25 kvClient, err := mongo.Connect(ctx, options.Client().ApplyURI(os.Getenv("ATLAS_URI"))) 26 if err != nil { 27 log.Fatal(err) 28 } 29 kvClient.Database("keyvault").Collection("datakeys").Drop(ctx) 30 clientEncryptionOpts := options.ClientEncryption().SetKeyVaultNamespace("keyvault.datakeys").SetKmsProviders(kmsProviders) 31 clientEncryption, err := mongo.NewClientEncryption(kvClient, clientEncryptionOpts) 32 if err != nil { 33 log.Fatal(err) 34 } 35 defer clientEncryption.Close(ctx) 36 dataKeyId, err := clientEncryption.CreateDataKey(ctx, "local", options.DataKey()) 37 if err != nil { 38 log.Fatal(err) 39 } 40 return dataKeyId 41 } 42 43 func createEncryptedClient(dataKeyIdBase64 string) *mongo.Client { 44 schemaMap = readSchemaFromFile("schema.json", dataKeyIdBase64) 45 mongocryptdOpts := map[string]interface{}{ 46 "mongodcryptdBypassSpawn": true, 47 } 48 autoEncryptionOpts := options.AutoEncryption(). 49 SetKeyVaultNamespace("keyvault.datakeys"). 50 SetKmsProviders(kmsProviders). 51 SetSchemaMap(schemaMap). 52 SetExtraOptions(mongocryptdOpts) 53 mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(os.Getenv("ATLAS_URI")).SetAutoEncryptionOptions(autoEncryptionOpts)) 54 if err != nil { 55 log.Fatal(err) 56 } 57 return mongoClient 58 } 59 60 func readSchemaFromFile(file string, dataKeyIdBase64 string) bson.M { 61 content, err := ioutil.ReadFile(file) 62 if err != nil { 63 log.Fatal(err) 64 } 65 content = []byte(fmt.Sprintf(string(content), dataKeyIdBase64)) 66 var doc bson.M 67 if err = bson.UnmarshalExtJSON(content, false, &doc); err != nil { 68 log.Fatal(err) 69 } 70 return doc 71 } 72 73 func main() { 74 fmt.Println("Starting the application...") 75 localKey := make([]byte, 96) 76 if _, err := rand.Read(localKey); err != nil { 77 log.Fatal(err) 78 } 79 kmsProviders = map[string]map[string]interface{}{ 80 "local": { 81 "key": localKey, 82 }, 83 } 84 dataKeyId := createDataKey() 85 client := createEncryptedClient(base64.StdEncoding.EncodeToString(dataKeyId.Data)) 86 defer client.Disconnect(ctx) 87 collection := client.Database("fle-example").Collection("people") 88 collection.Drop(context.TODO()) 89 if _, err := collection.InsertOne(context.TODO(), bson.M{"name": "Nic Raboy", "ssn": "123456"}); err != nil { 90 log.Fatal(err) 91 } 92 result, err := collection.FindOne(context.TODO(), bson.M{"ssn": "123456"}).DecodeBytes() 93 if err != nil { 94 log.Fatal(err) 95 } 96 fmt.Println(result) 97 }
Tente definir
ATLAS_URI
em suas variáveis de ambiente e dê uma olhada no código.Se você executou o código acima e encontrou alguns dados criptografados em seu banco de dados, fantástico! No entanto, se você não teve tanta sorte, quero abordar alguns dos problemas comuns que surgem.
Vamos começar com o seguinte erro de tempo de execução:
1 panic: client-side encryption not enabled. add the cse build tag to support
Se você ver o erro acima, é provável que tenha esquecido de usar o sinalizador
-tags cse
ao criar ou executar seu aplicativo. Para ir além disso, basta criar seu aplicativo com o seguinte:1 go build -tags cse
Supondo que não haja outros problemas, você não receberá mais esse erro.
Ao construir ou executar com a bandeira
-tags cse
, você pode encontrar o seguinte erro:1 /usr/local/Cellar/go/1.13.1/libexec/pkg/tool/darwin_amd64/link: running clang failed: exit status 1 2 ld: warning: directory not found for option '-L/usr/local/Cellar/libmongocrypt/1.0.4/lib' 3 ld: library not found for -lmongocrypt 4 clang: error: linker command failed with exit code 1 (use -v to see invocation)
O erro pode não parecer exatamente igual ao meu, dependendo do sistema operacional que você está usando, mas a questão principal é que ele diz que está faltando a bibliotecalibmongocrypt. Certifique-se de instalá-lo corretamente para seu sistema operacional, de acordo com a documentação.
```Agora, e se você encontrar o seguinte?```
1 exec: "mongocryptd": executable file not found in $PATH 2 exit status 1
Como no errolibmongocrypt, isso significa apenas que não temos acesso a mongocryptd, um requisito para a criptografia automática em nível de campo. Existem vários métodos para instalar esse binário, como visto na documentação, mas no macOS isso significa ter o MongoDB Enterprise Edition por perto.
Você acabou de ver como usar a criptografia no nível do campo do lado do cliente (CSFLE) do MongoDB em seu aplicativo Go. Isso é útil se você quiser criptografar campos dentro de documentos do MongoDB no lado do cliente antes que eles cheguem ao banco de dados.
Para dar crédito onde o crédito é devido, grande parte do código deste tutorial foi retirado do repositório de sandboxde Kern White no Github.
Há algumas coisas que gostaria de reiterar:
- Usar uma chave local é um risco de segurança na produção. Use algo como o AWS KMS ou carregue seu provedor de chaves locais com uma chave que foi obtida por meio de uma solicitação externa.
- O bináriomongocryptd deve estar disponível no computador ou servidor que estiver executando o aplicativo Go. Ele é facilmente instalado por meio da instalação do MongoDB Enterprise Edition.
- A bibliotecalibmongocrypt deve estar disponível para adicionar compatibilidade ao driver Go para criptografia e descriptografia no lado do cliente.
- Não perde sua chave do lado do cliente. Caso contrário, você perde a capacidade de descriptografar seus campos.
Em um tutorial futuro, exploraremos como usar Amazon Web Services KMS e semelhantes para gerenciamento de chaves.
Questões? Comentários? Queremos muito nos conectar com você. Participe da conversa nos fóruns da MongoDB Community.