在没有外部包的情况下在 GO 中存储基本 HTTP AUth user/password 凭据
Store Basic HTTP AUth user/password credentials in GO without external packages
我正在开发一个简单的博客引擎,只使用标准库(和 mysql 驱动程序)
对于管理员,我使用的是基本 HTTP 身份验证
func IsAllowed(w http.ResponseWriter, r *http.Request) bool {
u, p, ok := r.BasicAuth()
if !ok {
w.Header().Set("WWW-Authenticate", `Basic realm="Beware! Protected REALM! "`)
w.WriteHeader(401)
w.Write([]byte("401 Unauthorized\n"))
return false
}
if u != "devnull" || p != "veryfancypw" {
w.Header().Set("WWW-Authenticate", `Basic realm="Beware! Protected REALM! "`)
w.WriteHeader(401)
w.Write([]byte("401 Unauthorized\n"))
return false
}
return true
}
这显然不理想,因为不应以这种方式对用户密码进行硬编码。
存储这些凭据的最佳方式是什么?在配置文件中但没有外部包?当我启动 go 运行 main.go 或 ?
时是否需要将其作为参数传递
更新
我接受了@stdtom 的回复,因为它非常全面。我选择存储在环境变量中,所以我们有:
func IsAllowed(w http.ResponseWriter, r *http.Request) bool {
u, p, ok := r.BasicAuth()
boss := os.Getenv("BOSS")
bosspw := os.Getenv("BOSSPW")
// printf("Debug: %s, debugging: %s\n", boss, bosspw)
if !ok {
w.Header().Set("WWW-Authenticate", `Basic realm="Beware! Protected REALM! "`)
w.WriteHeader(401)
w.Write([]byte("401 Unauthorized\n"))
return false
}
if u != boss || p != bosspw {
w.Header().Set("WWW-Authenticate", `Basic realm="Beware! Protected REALM! "`)
w.WriteHeader(401)
w.Write([]byte("401 Unauthorized\n"))
return false
}
return true
}
为此,您需要在 .zshrc 中添加这些变量(在 mac 上)或者如果您使用 bash .bashrc
export BOSS=myusername
export BOSSPW="my long and difficult to get password"
正如接受的回复所建议的那样“特权用户可以访问环境变量...”但是我的场景是基于您的 运行 宁您自己的 machine 所以如果有人可以访问它您博客的管理界面 user/pw 可能是您遇到的最少的问题。
我认为这非常有用,因为您在网上找到的关于在 GO 中使用基本 HTTP 身份验证的大多数示例只显示硬编码的用户名和密码,这当然是一个非常糟糕的主意。
归结为在服务器或其他运行时环境中存储凭据时,您不知何故介于魔鬼和深蓝色的大海之间。没有真正好的解决方案同样可用。
开始问问自己,您的威胁模型是什么。
- A:秘密在版本控制中持续存在,与他人共享,或者更糟的是,在 GitHub 上制作 public 等
- B:秘密暴露给运行时环境的非特权co-users
- C:秘密暴露给运行时环境的特权用户(包括破坏系统并能够获得特权用户权限的攻击者)。
根据定义的威胁,您可以开始评估存储和注入机密的潜在解决方案。这当然取决于您的环境(例如 OS、云提供商、Kubernetes/Docker 等)。在下文中,我将假设 Linux 为 OS.
作为参数传入:
会减轻威胁 A,但不会减轻威胁 B 和 C。命令行参数甚至可以被非特权用户泄露,例如通过 ps -eo args
存储在配置文件中:
假设文件权限设置正确,将减轻威胁 B。关于A,仍然存在配置文件被无意添加到版本控制中的风险。不减轻威胁 C.
如果你会使用例如json 配置文件的格式,可以使用 Golang 标准库轻松实现。
存放在环境变量中:
会减轻威胁 A 和 B,但不会减轻威胁 C。特权用户可以通过 /proc/<pid>/environ
访问环境变量。此外,问题仍然是如何在运行时环境中设置环境变量。如果您使用 CI/CD 管道部署服务,则此管道可用于在部署期间注入环境变量。通常,CI/CD 引擎带有某种变量存储秘密。
这种方法的缺点是环境变量是短暂的,因此在重新启动运行时环境后,您需要通过 CI/CD 管道重新部署,或者您需要确保秘密在运行时环境,例如在启动脚本中。
可以使用标准库中的 os.Getenv()
或 os.LookupEnv()
轻松读取环境变量。
开始时间手动输入:
会缓解 A 和 B,但特权用户仍然能够从内存中读取秘密。重新启动运行时环境后,服务将不可用,直到操作员手动输入机密。因此,在许多用例中,这种方法可能会被认为是不切实际的。
进一步考虑:
按照 brianmac 的建议在数据库中存储秘密将问题转移到“在哪里存储我的数据库凭据?”
将秘密加密与上述任何解决方案相结合将需要解密密钥可供运行时环境中的服务使用。因此,您要么需要 TPM-based 解决方案,要么面临将此密钥存储在何处的问题。
“机密即服务”解决方案,如 Hashicorp Vault、Azure Key Vault、AWS Secrets Manager 等,在您的场景中可能会过大。它们提供秘密的集中存储和管理。 Applications/services 可以通过定义的 API.
从这个解决方案中检索秘密
但是,这需要对请求机密的服务进行身份验证和授权。所以我们回到了如何在运行时环境中为服务存储另一个秘密的问题。
云提供商试图通过为运行时环境分配身份并授权此身份访问其他云资源(包括“秘密即服务”解决方案)来克服这一问题。通常只有指定的运行时环境才能检索身份的凭据。但是,没有什么可以阻止有权访问运行时环境的特权用户使用身份访问机密。
最重要的是,很难甚至不可能以特权用户或破坏系统的人无法访问的方式存储机密。
如果您将此视为剩余风险,将秘密存储在环境变量中是一个很好的方法,因为它可以避免永久保存秘密。它还与平台无关,因此可以与任何运行时环境、云提供商等一起使用。它还可以得到各种自动化和部署工具的支持。
我正在开发一个简单的博客引擎,只使用标准库(和 mysql 驱动程序)
对于管理员,我使用的是基本 HTTP 身份验证
func IsAllowed(w http.ResponseWriter, r *http.Request) bool {
u, p, ok := r.BasicAuth()
if !ok {
w.Header().Set("WWW-Authenticate", `Basic realm="Beware! Protected REALM! "`)
w.WriteHeader(401)
w.Write([]byte("401 Unauthorized\n"))
return false
}
if u != "devnull" || p != "veryfancypw" {
w.Header().Set("WWW-Authenticate", `Basic realm="Beware! Protected REALM! "`)
w.WriteHeader(401)
w.Write([]byte("401 Unauthorized\n"))
return false
}
return true
}
这显然不理想,因为不应以这种方式对用户密码进行硬编码。
存储这些凭据的最佳方式是什么?在配置文件中但没有外部包?当我启动 go 运行 main.go 或 ?
时是否需要将其作为参数传递更新
我接受了@stdtom 的回复,因为它非常全面。我选择存储在环境变量中,所以我们有:
func IsAllowed(w http.ResponseWriter, r *http.Request) bool {
u, p, ok := r.BasicAuth()
boss := os.Getenv("BOSS")
bosspw := os.Getenv("BOSSPW")
// printf("Debug: %s, debugging: %s\n", boss, bosspw)
if !ok {
w.Header().Set("WWW-Authenticate", `Basic realm="Beware! Protected REALM! "`)
w.WriteHeader(401)
w.Write([]byte("401 Unauthorized\n"))
return false
}
if u != boss || p != bosspw {
w.Header().Set("WWW-Authenticate", `Basic realm="Beware! Protected REALM! "`)
w.WriteHeader(401)
w.Write([]byte("401 Unauthorized\n"))
return false
}
return true
}
为此,您需要在 .zshrc 中添加这些变量(在 mac 上)或者如果您使用 bash .bashrc
export BOSS=myusername
export BOSSPW="my long and difficult to get password"
正如接受的回复所建议的那样“特权用户可以访问环境变量...”但是我的场景是基于您的 运行 宁您自己的 machine 所以如果有人可以访问它您博客的管理界面 user/pw 可能是您遇到的最少的问题。
我认为这非常有用,因为您在网上找到的关于在 GO 中使用基本 HTTP 身份验证的大多数示例只显示硬编码的用户名和密码,这当然是一个非常糟糕的主意。
归结为在服务器或其他运行时环境中存储凭据时,您不知何故介于魔鬼和深蓝色的大海之间。没有真正好的解决方案同样可用。
开始问问自己,您的威胁模型是什么。
- A:秘密在版本控制中持续存在,与他人共享,或者更糟的是,在 GitHub 上制作 public 等
- B:秘密暴露给运行时环境的非特权co-users
- C:秘密暴露给运行时环境的特权用户(包括破坏系统并能够获得特权用户权限的攻击者)。
根据定义的威胁,您可以开始评估存储和注入机密的潜在解决方案。这当然取决于您的环境(例如 OS、云提供商、Kubernetes/Docker 等)。在下文中,我将假设 Linux 为 OS.
作为参数传入:
会减轻威胁 A,但不会减轻威胁 B 和 C。命令行参数甚至可以被非特权用户泄露,例如通过 ps -eo args
存储在配置文件中: 假设文件权限设置正确,将减轻威胁 B。关于A,仍然存在配置文件被无意添加到版本控制中的风险。不减轻威胁 C.
如果你会使用例如json 配置文件的格式,可以使用 Golang 标准库轻松实现。
存放在环境变量中:
会减轻威胁 A 和 B,但不会减轻威胁 C。特权用户可以通过 /proc/<pid>/environ
访问环境变量。此外,问题仍然是如何在运行时环境中设置环境变量。如果您使用 CI/CD 管道部署服务,则此管道可用于在部署期间注入环境变量。通常,CI/CD 引擎带有某种变量存储秘密。
这种方法的缺点是环境变量是短暂的,因此在重新启动运行时环境后,您需要通过 CI/CD 管道重新部署,或者您需要确保秘密在运行时环境,例如在启动脚本中。
可以使用标准库中的 os.Getenv()
或 os.LookupEnv()
轻松读取环境变量。
开始时间手动输入: 会缓解 A 和 B,但特权用户仍然能够从内存中读取秘密。重新启动运行时环境后,服务将不可用,直到操作员手动输入机密。因此,在许多用例中,这种方法可能会被认为是不切实际的。
进一步考虑:
按照 brianmac 的建议在数据库中存储秘密将问题转移到“在哪里存储我的数据库凭据?”
将秘密加密与上述任何解决方案相结合将需要解密密钥可供运行时环境中的服务使用。因此,您要么需要 TPM-based 解决方案,要么面临将此密钥存储在何处的问题。
“机密即服务”解决方案,如 Hashicorp Vault、Azure Key Vault、AWS Secrets Manager 等,在您的场景中可能会过大。它们提供秘密的集中存储和管理。 Applications/services 可以通过定义的 API.
从这个解决方案中检索秘密但是,这需要对请求机密的服务进行身份验证和授权。所以我们回到了如何在运行时环境中为服务存储另一个秘密的问题。
云提供商试图通过为运行时环境分配身份并授权此身份访问其他云资源(包括“秘密即服务”解决方案)来克服这一问题。通常只有指定的运行时环境才能检索身份的凭据。但是,没有什么可以阻止有权访问运行时环境的特权用户使用身份访问机密。
最重要的是,很难甚至不可能以特权用户或破坏系统的人无法访问的方式存储机密。
如果您将此视为剩余风险,将秘密存储在环境变量中是一个很好的方法,因为它可以避免永久保存秘密。它还与平台无关,因此可以与任何运行时环境、云提供商等一起使用。它还可以得到各种自动化和部署工具的支持。