概述
大多数开发人员完全意识到,将敏感信息存储在代码中是不好的做法。如果未经授权的用户可以访问您的代码库,那么该个人现在可以使用试图保护的任何信息。例如,如果您要在代码库中存储行星尺度连接字符串,那么任何访问代码的人现在都可以直接访问您的数据库并造成各种破坏。
避免这种情况的一种简单方法是使用环境变量,以便可以将敏感信息存储在运行代码的系统中,而不是代码本身。如果您在AWS中构建后端服务,实际上,有一种方法可以通过使用AWS KMS service中的托管密钥对这些环境变量进行进一步保护。本文解释了什么是kms以及如何在lambda函数中使用它。
要与本教程一起,您将需要以下内容:
- A PlanetScale account.
- PlanetScale CLI is installed and configured或预先存在的数据库和连接字符串。
- Go,以及对语言的基本理解(尽管您熟悉GO,也对代码样本进行了大量评论)。
- An AWS account.
请注意,我们将在AWS中创建资源,这花费了真实的钱。资源可能会在AWS用户的免费层覆盖,因此请检查您的AWS计划。
由于这是一个以安全为重点的话题,也建议您了解AWS Shared Responsibility Model。
什么是kms
AWS Key Management Service(kms)是AWS提供的服务,可让您从一个位置管理加密密钥。它使您可以轻松地生成对称或不对称的键,或上传您生成的键,可用于加密和解密数据。
在考虑AWS安全模型时,它不仅可以决定使用这些钥匙获得哪些信息,还可以决定AWS中哪些服务可以访问以前使用AWS Identity和Access Management对其进行加密的键进行密码(IAM)(IAM)(IAM) )政策。它的设计方式也使AWS员工无法访问这些钥匙,因此即使在AWS中工作的个人也无法解密您的敏感信息。
教程概述
建议使用像kms这样的系统加密环境变量(例如行星尺度连接字符串),以确保最大的安全性。让我们看一下如何使用lambda函数来执行此操作。这是我们在本文其余部分中所看的内容的概述:
- 您将通过创建一个读取来自行星尺度数据库的一些数据的lambda函数。
- 然后,您将在kms中创建一个托管密钥。
- 接下来,将加密存储连接字符串的环境变量,这将导致函数错误。
- 最后,您将更新代码以解密连接字符串,并再次从数据库中测试读取数据。
该项目使用的数据库将包含一个名为recipes
的表,该表具有以下结构:
+-------------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------------+--------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| name | varchar(100) | YES | | NULL | |
| est_time_to_make_in_min | int | YES | | NULL | |
| description | varchar(100) | YES | | NULL | |
+-------------------------+--------------+------+-----+---------+----------------+
如果您希望遵循相同的结构,则可以使用以下SQL命令在数据库中创建表并播种一些数据。这些可以使用Planetscale CLI或在仪表板中使用“控制台”选项卡运行。
CREATE TABLE recipes (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
est_time_to_make_in_min INT,
description VARCHAR(100)
);
INSERT INTO recipes (name, est_time_to_make_in_min, description) VALUES
('Goatsnake Pizza', 60, 'Lots of deliciousness'),
('Clutch Pizza', 45, 'This is a pretty awesome pizza too'),
('Pepperoni Pizza', 30, 'Spicy stuff!');
创建lambda功能
首先在计算机上创建一个新文件夹以保存GO项目。打开该目录中的终端并运行以下命令以初始化项目,用该项目的任意名称代替$PROJECT_NAME
:
go mod init $PROJECT_NAME
创建一个名为main.go
的文件并添加以下代码。该代码已评论,因此您将了解正在发生的事情:
package main
import (
"database/sql"
"encoding/json"
"log"
"os"
"github.com/aws/aws-lambda-go/lambda"
_ "github.com/go-sql-driver/mysql"
)
// The Recipe model will hold the data for a record pulled from the database.
type Recipe struct {
Id int
Name string
EstTimeToMake int
Description string
}
// Sets up the connection to the PlanetScale database.
func GetDatabase() (*sql.DB, error) {
db, err := sql.Open("mysql", os.Getenv("DSN"))
return db, err
}
// The function that will be called when the Lambda function is invoked.
func handler() {
// Get a database connection
db, err := GetDatabase()
if err != nil {
panic(err)
}
// Run the query to fetch all recipes
query := "SELECT * FROM recipes;"
res, err := db.Query(query)
if err != nil {
panic(err)
}
// Loop through the results, placing them in an array of the Recipe struct
var recipes []Recipe
for res.Next() {
var r Recipe
res.Scan(&r.Id, &r.Name, &r.EstTimeToMake, &r.Description)
recipes = append(recipes, r)
}
// Convert the results to a JSON string and log out that string.
jbytes, err := json.Marshal(recipes)
if err != nil {
panic(err)
}
log.Printf("Returned recipes: %v", string(jbytes))
}
// The main entry point for the Lambda service.
func main() {
lambda.Start(handler)
}
现在返回终端并运行以下命令以安装任何缺失的依赖项:
go mod tidy
接下来,您将需要构建功能并将二进制汇编,以便将其上传到lambda服务。您需要为GOARCH
和GOOS
设置环境变量,因此编译器为Lambda环境创建了适当的二进制文件。运行以下命令之一(取决于操作系统)以在dist
文件夹中创建二进制文件:
// Mac/Linux
GOARCH=amd64 GOOS=linux go build -o dist/main .
// Windows
$Env:GOARCH="amd64"; $Env:GOOS="linux"; go build -o dist/main .
将功能上传到AWS之前的最后一步是将创建的二进制文件添加到ZIP文件中,因为这是Lambda服务的期望。在Mac上,您可以右键单击main
文件并压缩它以创建该文件。
设置并测试AWS中的lambda功能
下一步是在AWS中配置lambda函数,并在上一节中上传zpipted文件夹。在AWS中,使用全局搜索查找lambdaâ,然后从结果列表中选择它。
单击“创建函数” 启动向导以创建lambda函数。
给该功能一个名称,然后更改“运行时” go 1.x 。留下其余默认值,然后单击“创建函数” 在表单的底部。
创建功能后,您将需要上传上一节中创建的zip文件。从“代码” 源部分中,选择“从“ > ”上传。
在下一个模式中,单击“上传” 按钮,然后从计算机中选择zip文件。单击“保存” 一旦选择了它。 接下来,您需要将默认处理程序从 将处理程序更改为主字段,然后单击“保存” 。 接下来,选择“配置” tab> “环境变量” > “ edit” 。 创建一个名为“ DSN”的条目,并在您的PlanetsCale数据库的连接字符串中粘贴。您可以通过单击“连接”,单击“与“下拉”连接,然后选择“ go”来找到它。一旦您拥有它,然后将其粘贴并单击“保存” 。 最后,让我们测试功能,看看是否从数据库中获取数据。选择“测试” 选项卡,然后单击“测试” 按钮。 视图应更新并显示一个称为执行结果的警报框。如果您正确遵循所有前一步,则该盒子应该为绿色。展开它,您应该从日志输出下的数据库中查看记录。 现在,让我们看看如何用kms键加密连接字符串。在从Lambda继续前进之前,您需要担任此Lambda的执行角色。您可以在“配置” 选项卡中找到“权限” 。在下一步中需要注意它。 从AWS控制台开始,然后使用全局搜索查找密钥管理服务。从可用服务列表中选择它。 如果您看不到立即创建键的按钮,请先从左导航中选择“客户托管密钥” 。单击“创建键” 。 如前所述,AWS使您可以创建对称和不对称的键。这两种选项都可以用于加密和解密数据,但是如果您需要下载用于签署AWS之外的其他工件的公共密钥,则不对称密钥很有用。由于我们仅在AWS中工作,因此选择“对称” 选择,然后单击“ Next” 。 在别名下的下一个视图中
现在,您需要配置密钥管理员,该管理员可以是IAM用户,组或角色。密钥管理员是允许从AWS控制台或API对密钥更改的用户。对于本教程,选择您自己的IAM用户帐户。向下滚动,然后单击“ Next” 。 下一个视图将使您选择允许在kms中访问密钥的IAM用户,组或角色。从上一节键入Lambda函数的执行角色名称,然后从列表中选择它。单击“ Next” 一旦选择了它。 最后,滚动到底部,然后单击“完成” 。 回到您的lambda功能,选择“配置” tab> “环境变量” >> “ edit” 。 现在扩展加密配置部分。检查“启用助手在运输中进行加密” 框,您会注意到加密按钮现在存在于DSN环境变量旁边。 单击“加密” 时,将出现一种模式,您可以在上一节中选择创建的kms键。如果您扩展了解密秘密片段,您还可以向您显示您可以使用的代码,以将其拉入加密值并将其解密以在代码中使用。我们将将其添加到lambda函数中。选择您的kms键,然后单击“加密” 。 DSN环境变量的值应更新为加密值。单击“保存” 。 现在,如果您尝试再次测试代码,则应该失败,因为代码不知道该如何处理加密的连接字符串。注意如何专门围绕MySQL驱动程序弄清楚如何连接到Planetscale数据库的错误。 要解决此问题,请在计算机上再次打开 现在遵循上一节的过程以构建项目,将其缩进并将其上传到AWS中。这样做后,请再次在AWS中测试该功能,并应成功返回数据。 如果您遵循的话,您应该对如何使用KMS在AWS上构建的应用程序中加密敏感信息有很好的了解。这是存储连接字符串的一种更安全的方法,因此即使您的AWS帐户受到损害,未经授权的用户也无法访问您的行星标度数据库。在此处使用的示例时,无论语言如何,任何应用程序都适用于任何应用。hello
更改为main
,这是为lambda构建的二进制文件的名称。在运行时设置下,单击“编辑” 。
在KMS中创建客户托管密钥
加密lambda中的连接字符串
main.go
,然后更新文件的前半部分(通过GetDatabase()
),以如下图。将更新导入,将添加init()
函数,并将更新GetDatabase()
函数以反映符合解密连接字符串的DSN
变量。
package main
import (
"database/sql"
"encoding/json"
"log"
"os"
"encoding/base64"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/kms"
_ "github.com/go-sql-driver/mysql"
)
// Set up variables to be used with the encrypted connection string.
var functionName string = os.Getenv("AWS_LAMBDA_FUNCTION_NAME")
var encrypted string = os.Getenv("DSN")
var DSN string
// The init function will run first, decrypting DNS into the above variable.
func init() {
kmsClient := kms.New(session.New())
decodedBytes, err := base64.StdEncoding.DecodeString(encrypted)
if err != nil {
panic(err)
}
input := &kms.DecryptInput{
CiphertextBlob: decodedBytes,
EncryptionContext: aws.StringMap(map[string]string{
"LambdaFunctionName": functionName,
}),
}
response, err := kmsClient.Decrypt(input)
if err != nil {
panic(err)
}
DSN = string(response.Plaintext[:])
}
// The Recipe model will hold the data for a record pulled from the database.
type Recipe struct {
Id int
Name string
EstTimeToMake int
Description string
}
// Sets up the connection to the PlanetScale database.
func GetDatabase() (*sql.DB, error) {
db, err := sql.Open("mysql", DSN) // ← Update the second parameter here
return db, err
}
// remainder of the code...
结论