在git仓库中处理密码的最佳实践是什么?


225

我有一个Bash脚本,可用于访问Twitter并在某些情况下弹出Growl通知。用脚本存储密码的最佳方法是什么?

我想将此脚本提交到git repo并使其在GitHub上可用,但是我想知道在执行此操作时保持我的登录名/密码为私有的最佳方法是什么。当前,密码存储在脚本本身中。我不能在推送之前立即删除它,因为所有旧提交都将包含密码。没有密码进行开发不是一种选择。我想我应该将密码存储在一个外部配置文件中,但是我想我会先检查一下是否有确定的方法来处理此问题,然后再尝试将它们放在一起。

Answers:


256

典型的方法是从配置文件中读取密码信息。如果您的配置文件名为foobar.config,那么您将向foobar.config.example存储库提交一个包含示例数据的文件。要运行程序,您将创建一个foobar.config使用真实密码数据调用的本地(未跟踪)文件。

要从以前的提交中过滤掉您现有的密码,请参阅删除敏感数据上的GitHub帮助页面。


4
顺便说一句,您可以将示例foobar.config添加到存储库,然后将foobar.config添加到.ignore文件。这样,示例foobar.config会在克隆时出现,并且您的实际密码不会添加到存储库中。
Mr_Chimp

16
@Mr_Chimp:该.gitignore文件不适用于存储库中跟踪的文件。例如,git add -u即使已存在,也会添加一个已更改的文件.gitignore
Greg Hewgill

1
作为补充,这里如果你增加意外配置文件中一个有趣的链接,你想从git的历史记录中删除它:help.github.com/articles/remove-sensitive-data
卢瓦克·洛佩斯

16
您将如何与团队共享这些密码?一件事是拥有本地副本(不提交给仓库),另一件事是与更大的团队共享,甚至使用自动工具(用于部署等)
共享

2
我和@dangonfast有相同的问题。对于大型团队来说,这似乎不切实际。
Jacob Stamm

25

一种方法是使用环境变量设置密码(或API密钥)。因此,此密码不受版本控制。

使用Bash,您可以使用以下命令设置环境变量

export your_env_variable='your_password'

这种方法可以与Travis之类的持续集成服务一起使用,您存储在GitHub存储库中的代码(无密码)可以由Travis执行(使用环境变量设置密码)。

使用Bash,您可以使用以下方法获取环境变量的值:

echo "$your_env_variable"

使用Python,您可以使用以下方法获取环境变量的值:

import os
print(os.environ['your_env_variable'])

PS:请注意,这可能有点冒险(但这是很常见的做法)https://www.bleepingcomputer.com/news/security/javascript-packages-caught-stealing-environment-variables/

PS2:dev.to这篇标题为“如何安全存储API密钥”的文章可能很有趣。


1
如何防止潜在的“不安全”的代码蜂群构建读取环境变量的内容?
gorootde


16

格雷格说了什么,但我要补充一点,检入文件是个好主意foobar.config-TEMPLATE

它应包含示例名称,密码或其他配置信息。那么很明显,真正的foobar.config应该包含什么,而不必查看所有必须以foobar.config何种形式存在值以及其应采用何种格式的代码。

通常,配置值可能不明显,例如数据库连接字符串和类似内容。


7

根据您的确切问题,将以不同的方式处理存储库中的密码。

1.不要这样做。

并在某些答复中介绍了避免这样做的方法-.gitignore,config.example等

或2.使存储库仅可由授权人员访问

即允许人们知道密码的人。chmod和用户群浮现在脑海;如果您在外部托管存储库或服务器,还应该允许Github或AWS员工看到问题吗?

或3.加密敏感数据(此回复的目的)

如果要将包含敏感信息(例如密码)的配置文件存储在公共位置,则需要对其进行加密。从存储库中恢复文件时,可以对其进行解密,甚至可以直接使用其加密形式来使用。

下面显示了使用加密的配置数据的示例javascript解决方案。

const fs = require('fs');
const NodeRSA = require('node-rsa');

let privatekey = new NodeRSA();
privatekey.importKey(fs.readFileSync('private.key', 'utf8'));
const config = privatekey.decrypt(fs.readFileSync('config.RSA', 'utf8'), 'json');

console.log('decrypted: ', config);

解密的配置文件

因此,您可以恢复仅编写几行Javascript的加密配置文件。

请注意,将文件config.RSA放入git存储库将有效地使其成为二进制文件,因此它将失去诸如Git之类的许多好处,例如,能够自动选择更改。

解决方案可能是加密键值对或仅加密值。您可以加密所有值,例如,如果您具有敏感信息的单独文件,或者如果一个文件中具有所有值,则仅加密敏感值。(见下文)

上面的示例对于想要对其进行测试的任何人都是无用的,或者作为一个示例,因为它假设存在一些RSA密钥和一个加密的配置文件,因此从此开始config.RSA

因此,这里添加了一些额外的代码行来创建RSA密钥和一个配置文件。

const fs = require('fs');
const NodeRSA = require('node-rsa');

/////////////////////////////
// Generate some keys for testing
/////////////////////////////

const examplekey = new NodeRSA({b: 2048});

fs.writeFileSync('private.key', examplekey.exportKey('pkcs8-private'));
fs.writeFileSync('public.key', examplekey.exportKey('pkcs8-public'));

/////////////////////////////
// Do this on the Machine creating the config file
/////////////////////////////

const configToStore = {Goodbye: 'Cruel world'};

let publickey = new NodeRSA();
publickey.importKey(fs.readFileSync('public.key', 'utf8'));

fs.writeFileSync('config.RSA', publickey.encrypt(configToStore, 'base64'), 'utf8');

/////////////////////////////
// Do this on the Machine consuming the config file
/////////////////////////////

let privatekey = new NodeRSA();
privatekey.importKey(fs.readFileSync('private.key', 'utf8'));

const config = privatekey.decrypt(fs.readFileSync('config.RSA', 'utf8'), 'json');
console.log('decrypted: ', config);

仅加密值

fs.writeFileSync('config.RSA', JSON.stringify(config,null,2), 'utf8');

在此处输入图片说明

您可以使用类似的方法解密带有加密值的配置文件。

const savedconfig = JSON.parse(fs.readFileSync('config.RSA', 'utf8'));
let config = {...savedconfig};
Object.keys(savedconfig).forEach(key => {
    config[key] = privatekey.decrypt(savedconfig[key], 'utf8');
});

通过将每个配置项放在单独的行上(例如HelloGoodbye以上),Git将更好地识别文件中发生的事情,并将对信息项的更改存储为差异而不是完整文件。Git也将能够更好地管理合并和樱桃采摘等。

但是,您越想对敏感信息进行版本控制更改,就越倾向于SAFE REPOSITORY解决方案(2)而远离ENCRYPTED INFO(3)解决方案。


3

可以使用Vault来保护,存储和控制对令牌,密码,证书,API密钥等的访问。例如Ansible使用Ansible Vault来处理剧本中使用的密码或证书。


与仅创建示例配置文件相比,我发现Ansible Vault确实过于复杂。
icc97

@ icc97是的,这真可悲。但是我们需要提到这种可能性。我认为,对于较复杂的任务,那么为单用户环境存储少量密码最好从一开始就使用专门的解决方案。
El Ruso'5

2
为了帮助将来的读者:保管箱和Ansible Vault是完全不同的,互不相关的项目,它们的名称相似
bltavares

2

这是我使用的一种技术:

我在主文件夹中创建一个文件夹: .config

在该文件夹中,我放置了配置文件,以容纳要外部化密码和密钥的任何数量的内容。

我通常使用反向域名语法,例如:

com.example.databaseconfig

然后在bash脚本中执行以下操作:

#!/bin/bash
source $HOME/.config/com.example.databaseconfig ||exit 1

|| exit 1如果脚本无法加载配置文件,则会导致脚本退出。

我将这种技术用于bash,python和ant脚本。

我非常偏执,并且认为.gitignore文件没有足够强壮的功能来防止意外签入。另外,没有任何东西可以监视它,因此,如果签入确实发生了,那么没人会找到解决方案。

如果一个特定的应用程序需要多个文件,我将创建子文件夹而不是单个文件。


1

如果您在轨道上使用红宝石,那么Figaro宝石非常好,简单且可靠。它对生产环境的影响也很小。


4
您能否提供有关该宝石功能的详细信息?这样,它可以(潜在地)被视为适用于多种语言的“实践”。
mattumotu

medium.com/@MinimalGhost/…有一个概述,它似乎基本上可以管理从配置文件中提取内容的方式
Tripleee,

0

信任但要验证。

.gitignore这种情况下,将从存储库中排除“安全”目录:

secure/

但我分享@ 迈克尔·波特的偏执狂。因此,要验证.gitignore,以下是一个Python 单元测试,如果该“安全”目录被检入,它将引发klaxon。此外,还要检查合法目录,以检查该检查:

def test_github_not_getting_credentials(self):
    safety_url = 'https://github.com/BobStein/fliki/tree/master/static'
    danger_url = 'https://github.com/BobStein/fliki/tree/master/secure'

    self.assertEqual(200, urllib.request.urlopen(safety_url).status)

    with self.assertRaises(urllib.error.HTTPError):
        urllib.request.urlopen(danger_url)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.