开始使用 Python 进行安全 AWS CloudFront 流式传输

2024-05-07

我已经创建了一个 S3 存储桶,上传了一个视频,并在 CloudFront 中创建了一个流分配。用静态 HTML 播放器对其进行了测试,它可以工作。我已经通过帐户设置创建了密钥对。目前我的桌面上有私钥文件。这就是我所在的地方。

我的目标是让我的 Django/Python 网站创建安全 URL,并且人们无法访问视频,除非它们来自我的页面之一。问题是我对亚马逊的布局方式过敏,而且我越来越困惑。

我意识到这不会是 StackOverflow 上最好的问题,但我确信我不会是这里唯一一个无法弄清楚如何设置安全的 CloudFront/S3 情况的傻瓜。我非常感谢您的帮助,并且愿意(两天后)对最佳答案给予 500pt 的奖励。

我有几个问题,一旦得到解答,应该适合如何完成我所追求的目标的一种解释:

  • 在文档中(下一点有一个例子)有很多 XML 告诉我我需要POST东西到各个地方。有在线控制台可以执行此操作吗?或者我确实必须通过 cURL (等)强制执行此操作?

  • 如何为 CloudFront 创建源访问身份并将其绑定到我的分配?我读了这个文件 http://docs.amazonwebservices.com/AmazonCloudFront/latest/DeveloperGuide/index.html?SecuringContent_S3.html#CreateOriginAccessIdentity但是,根据第一点,不知道该怎么办。我的密钥对如何适应这个?

  • 完成后,如何限制 S3 存储桶仅允许人们通过该身份下载内容?如果这是另一个 XML 工作,而不是在 Web UI 上点击,请告诉我应该在哪里以及如何将其添加到我的帐户中。

  • 在 Python 中,为文件生成过期 URL 的最简单方法是什么。我有boto已安装,但我不知道如何从流媒体分发中获取文件。

  • 是否有任何应用程序或脚本可以解决设置此服装的困难?我使用 Ubuntu (Linux),但如果虚拟机仅支持 Windows,则我在虚拟机中安装了 XP。我已经研究过 CloudBerry S3 Explorer Pro - 但它与在线 UI 一样有意义。


你是对的,需要大量的 API 工作才能完成此设置。我希望他们很快就能在 AWS 控制台中获得它!

更新:我已将此代码提交给 boto - 从 boto v2.1(2011 年 10 月 27 日发布)开始,这变得更加容易。对于 boto http://www.secretmike.com/2011/10/aws-cloudfront-secure-streaming.html一旦 boto v2.1 被更多发行版打包,我将在这里更新答案。

要实现您想要的目标,您需要执行以下步骤,我将在下面详细说明:

  1. 创建您的 s3 存储桶并上传一些对象(您已经完成了此操作)
  2. 创建 Cloudfront“原始访问身份”(基本上是一个 AWS 帐户,以允许 Cloudfront 访问您的 s3 存储桶)
  3. 修改对象上的 ACL,以便仅允许您的 Cloudfront 原始访问身份读取它们(这可以防止人们绕过 Cloudfront 并直接访问 s3)
  4. 创建一个包含基本 URL 和需要签名 URL 的 Cloudfront 发行版
  5. 测试您是否可以从基本的cloudfront发行版下载对象,但不能从s3或签名的cloudfront发行版下载对象
  6. 创建用于签名 URL 的密钥对
  7. 使用Python生成一些URL
  8. 测试签名的 URL 是否有效

1 - 创建存储桶并上传对象

最简单的方法是通过 AWS 控制台,但为了完整起见,我将展示如何使用 boto。 Boto 代码如下所示:

import boto

#credentials stored in environment AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
s3 = boto.connect_s3()

#bucket name MUST follow dns guidelines
new_bucket_name = "stream.example.com"
bucket = s3.create_bucket(new_bucket_name)

object_name = "video.mp4"
key = bucket.new_key(object_name)
key.set_contents_from_filename(object_name)

2 - 创建 Cloudfront“原始访问身份”

目前,此步骤只能使用 API 来执行。 Boto代码在这里:

import boto

#credentials stored in environment AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
cf = boto.connect_cloudfront()

oai = cf.create_origin_access_identity(comment='New identity for secure videos')

#We need the following two values for later steps:
print("Origin Access Identity ID: %s" % oai.id)
print("Origin Access Identity S3CanonicalUserId: %s" % oai.s3_user_id)

3 - 修改对象上的 ACL

现在我们已经有了特殊的 S3 用户帐户(我们上面创建的 S3CanonicalUserId),我们需要授予它对 s3 对象的访问权限。我们可以使用 AWS 控制台轻松完成此操作,方法是打开对象的(不是存储桶的!)权限选项卡,单击“添加更多权限”按钮,然后将上面获得的很长的 S3CanonicalUserId 粘贴到新权限的“被授权者”字段中。确保您授予新权限“打开/下载”权限。

您还可以使用以下 boto 脚本在代码中执行此操作:

import boto

#credentials stored in environment AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
s3 = boto.connect_s3()

bucket_name = "stream.example.com"
bucket = s3.get_bucket(bucket_name)

object_name = "video.mp4"
key = bucket.get_key(object_name)

#Now add read permission to our new s3 account
s3_canonical_user_id = "<your S3CanonicalUserID from above>"
key.add_user_grant("READ", s3_canonical_user_id)

4 - 创建 Cloudfront 发行版

请注意,直到 2.0 版(在撰写本文时尚未正式发布)之前,boto 才完全支持自定义来源和私有发行版。下面的代码从 boto 2.0 分支中提取一些代码并将其组合在一起以使其运行,但它并不漂亮。 2.0 分支处理这个问题更加优雅 - 如果可能的话,绝对使用它!

import boto
from boto.cloudfront.distribution import DistributionConfig
from boto.cloudfront.exception import CloudFrontServerError

import re

def get_domain_from_xml(xml):
    results = re.findall("<DomainName>([^<]+)</DomainName>", xml)
    return results[0]

#custom class to hack this until boto v2.0 is released
class HackedStreamingDistributionConfig(DistributionConfig):

    def __init__(self, connection=None, origin='', enabled=False,
                 caller_reference='', cnames=None, comment='',
                 trusted_signers=None):
        DistributionConfig.__init__(self, connection=connection,
                                    origin=origin, enabled=enabled,
                                    caller_reference=caller_reference,
                                    cnames=cnames, comment=comment,
                                    trusted_signers=trusted_signers)

    #override the to_xml() function
    def to_xml(self):
        s = '<?xml version="1.0" encoding="UTF-8"?>\n'
        s += '<StreamingDistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2010-07-15/">\n'

        s += '  <S3Origin>\n'
        s += '    <DNSName>%s</DNSName>\n' % self.origin
        if self.origin_access_identity:
            val = self.origin_access_identity
            s += '    <OriginAccessIdentity>origin-access-identity/cloudfront/%s</OriginAccessIdentity>\n' % val
        s += '  </S3Origin>\n'


        s += '  <CallerReference>%s</CallerReference>\n' % self.caller_reference
        for cname in self.cnames:
            s += '  <CNAME>%s</CNAME>\n' % cname
        if self.comment:
            s += '  <Comment>%s</Comment>\n' % self.comment
        s += '  <Enabled>'
        if self.enabled:
            s += 'true'
        else:
            s += 'false'
        s += '</Enabled>\n'
        if self.trusted_signers:
            s += '<TrustedSigners>\n'
            for signer in self.trusted_signers:
                if signer == 'Self':
                    s += '  <Self/>\n'
                else:
                    s += '  <AwsAccountNumber>%s</AwsAccountNumber>\n' % signer
            s += '</TrustedSigners>\n'
        if self.logging:
            s += '<Logging>\n'
            s += '  <Bucket>%s</Bucket>\n' % self.logging.bucket
            s += '  <Prefix>%s</Prefix>\n' % self.logging.prefix
            s += '</Logging>\n'
        s += '</StreamingDistributionConfig>\n'

        return s

    def create(self):
        response = self.connection.make_request('POST',
            '/%s/%s' % ("2010-11-01", "streaming-distribution"),
            {'Content-Type' : 'text/xml'},
            data=self.to_xml())

        body = response.read()
        if response.status == 201:
            return body
        else:
            raise CloudFrontServerError(response.status, response.reason, body)


cf = boto.connect_cloudfront()

s3_dns_name = "stream.example.com.s3.amazonaws.com"
comment = "example streaming distribution"
oai = "<OAI ID from step 2 above like E23KRHS6GDUF5L>"

#Create a distribution that does NOT need signed URLS
hsd = HackedStreamingDistributionConfig(connection=cf, origin=s3_dns_name, comment=comment, enabled=True)
hsd.origin_access_identity = oai
basic_dist = hsd.create()
print("Distribution with basic URLs: %s" % get_domain_from_xml(basic_dist))

#Create a distribution that DOES need signed URLS
hsd = HackedStreamingDistributionConfig(connection=cf, origin=s3_dns_name, comment=comment, enabled=True)
hsd.origin_access_identity = oai
#Add some required signers (Self means your own account)
hsd.trusted_signers = ['Self']
signed_dist = hsd.create()
print("Distribution with signed URLs: %s" % get_domain_from_xml(signed_dist))

5 - 测试您是否可以从 cloudfront 下载对象,但不能从 s3 下载对象

您现在应该能够验证:

  • Stream.example.com.s3.amazonaws.com/video.mp4 - 应该给出 AccessDenied
  • signed_distribution.cloudfront.net/video.mp4 - 应该给出 MissingKey (因为 URL 未签名)
  • basic_distribution.cloudfront.net/video.mp4 - 应该可以正常工作

必须调整测试才能与您的流播放器配合使用,但基本思想是只有基本的 cloudfront url 才可以工作。

6 - 为 CloudFront 创建密钥对

我认为做到这一点的唯一方法是通过亚马逊网站。进入您的 AWS“帐户”页面并单击“安全凭证”链接。单击“密钥对”选项卡,然后单击“创建新密钥对”。这将为您生成一个新的密钥对并自动下载私钥文件(pk-xxxxxxxxx.pem)。确保密钥文件安全且私密。另请记下亚马逊的“密钥对 ID”,因为我们在下一步中将需要它。

7 - 在 Python 中生成一些 URL

从 boto 版本 2.0 开始,似乎不支持生成签名的 CloudFront URL。 Python 的标准库中不包含 RSA 加密例程,因此我们必须使用额外的库。我在这个例子中使用了 M2Crypto。

对于非流式传输分发,您必须使用完整的 cloudfront URL 作为资源,但是对于流式传输,我们仅使用视频文件的对象名称。请参阅下面的代码,了解生成仅持续 5 分钟的 URL 的完整示例。

此代码大致基于 Amazon 在 CloudFront 文档中提供的 PHP 示例代码。

from M2Crypto import EVP
import base64
import time

def aws_url_base64_encode(msg):
    msg_base64 = base64.b64encode(msg)
    msg_base64 = msg_base64.replace('+', '-')
    msg_base64 = msg_base64.replace('=', '_')
    msg_base64 = msg_base64.replace('/', '~')
    return msg_base64

def sign_string(message, priv_key_string):
    key = EVP.load_key_string(priv_key_string)
    key.reset_context(md='sha1')
    key.sign_init()
    key.sign_update(str(message))
    signature = key.sign_final()
    return signature

def create_url(url, encoded_signature, key_pair_id, expires):
    signed_url = "%(url)s?Expires=%(expires)s&Signature=%(encoded_signature)s&Key-Pair-Id=%(key_pair_id)s" % {
            'url':url,
            'expires':expires,
            'encoded_signature':encoded_signature,
            'key_pair_id':key_pair_id,
            }
    return signed_url

def get_canned_policy_url(url, priv_key_string, key_pair_id, expires):
    #we manually construct this policy string to ensure formatting matches signature
    canned_policy = '{"Statement":[{"Resource":"%(url)s","Condition":{"DateLessThan":{"AWS:EpochTime":%(expires)s}}}]}' % {'url':url, 'expires':expires}

    #now base64 encode it (must be URL safe)
    encoded_policy = aws_url_base64_encode(canned_policy)
    #sign the non-encoded policy
    signature = sign_string(canned_policy, priv_key_string)
    #now base64 encode the signature (URL safe as well)
    encoded_signature = aws_url_base64_encode(signature)

    #combine these into a full url
    signed_url = create_url(url, encoded_signature, key_pair_id, expires);

    return signed_url

def encode_query_param(resource):
    enc = resource
    enc = enc.replace('?', '%3F')
    enc = enc.replace('=', '%3D')
    enc = enc.replace('&', '%26')
    return enc


#Set parameters for URL
key_pair_id = "APKAIAZCZRKVIO4BQ" #from the AWS accounts page
priv_key_file = "cloudfront-pk.pem" #your private keypair file
resource = 'video.mp4' #your resource (just object name for streaming videos)
expires = int(time.time()) + 300 #5 min

#Create the signed URL
priv_key_string = open(priv_key_file).read()
signed_url = get_canned_policy_url(resource, priv_key_string, key_pair_id, expires)

#Flash player doesn't like query params so encode them
enc_url = encode_query_param(signed_url)
print(enc_url)

8 - 尝试 URL

希望您现在应该有一个工作 URL,如下所示:

video.mp4%3FExpires%3D1309979985%26Signature%3DMUNF7pw1689FhMeSN6JzQmWNVxcaIE9mk1x~KOudJky7anTuX0oAgL~1GW-ON6Zh5NFLBoocX3fUhmC9FusAHtJUzWyJVZLzYT9iLyoyfWMsm2ylCDBqpy5IynFbi8CUajd~CjYdxZBWpxTsPO3yIFNJI~R2AFpWx8qp3fs38Yw_%26Key-Pair-Id%3DAPKAIAZRKVIO4BQ

将其放入您的 js 中,您应该得到如下所示的内容(来自 Amazon CloudFront 文档中的 PHP 示例):

var so_canned = new SWFObject('http://location.domname.com/~jvngkhow/player.swf','mpl','640','360','9');
    so_canned.addParam('allowfullscreen','true');
    so_canned.addParam('allowscriptaccess','always');
    so_canned.addParam('wmode','opaque');
    so_canned.addVariable('file','video.mp4%3FExpires%3D1309979985%26Signature%3DMUNF7pw1689FhMeSN6JzQmWNVxcaIE9mk1x~KOudJky7anTuX0oAgL~1GW-ON6Zh5NFLBoocX3fUhmC9FusAHtJUzWyJVZLzYT9iLyoyfWMsm2ylCDBqpy5IynFbi8CUajd~CjYdxZBWpxTsPO3yIFNJI~R2AFpWx8qp3fs38Yw_%26Key-Pair-Id%3DAPKAIAZRKVIO4BQ');
    so_canned.addVariable('streamer','rtmp://s3nzpoyjpct.cloudfront.net/cfx/st');
    so_canned.write('canned');

Summary

正如您所看到的,这并不容易! boto v2 对设置发行版有很大帮助。我会看看是否可以在那里获取一些 URL 生成代码来改进这个伟大的库!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

开始使用 Python 进行安全 AWS CloudFront 流式传输 的相关文章

  • 在Python3.6中调用C#代码

    由于完全不了解 C 编码 我希望在我的 python 代码中调用 C 函数 我知道有很多关于同一问题的问答 但由于一些奇怪的原因 我无法从示例 python 模块导入简单的 c 类库 以下是我所做的事情 C 类库设置 我使用的是 VS 20
  • python 中分割字符串以获得一个值?

    需要帮助 假设我在名为 input 的变量中有一个字符串 Sam Person name kind input split 通过执行上述操作 我得到两个具有不同字符串 Sam 和 Person 的变量 有没有办法只获取第一个值 name S
  • 可以在 TensorFlow 中使用排名相关作为成本函数吗?

    我正在处理偶尔充满异常值的极其嘈杂的数据 因此我主要依靠相关性来衡量我的神经网络的准确性 是否可以明确使用诸如等级相关性 斯皮尔曼相关系数 之类的东西作为我的成本函数 到目前为止 我主要依赖 MSE 作为相关性的代理 我现在面临三个主要障碍
  • 优化 Keras 以使用所有可用的 CPU 资源

    好吧 我真的不知道我在说什么 所以请耐心听我说 我正在使用 Theano 后端运行 Keras 以在 MNIST 图像上运行基本的神经网络 目前只是一个教程 过去 我一直使用我的旧 HP 笔记本电脑 因为我有 Windows 和 Ubunt
  • Paramiko - 使用私钥连接 - 不是有效的 OPENSSH 私钥/公钥文件

    我正在尝试找到解决方案 但无法理解我做错了什么 在我的 Linux 服务器上 我运行了以下命令 ssh keygen t rsa 这产生了一个id rsa and id rsa pub file 然后我将它们复制到本地并尝试运行以下代码 s
  • Pandas重置索引未生效[重复]

    这个问题在这里已经有答案了 我不确定我在哪里误入歧途 但我似乎无法重置数据帧上的索引 当我跑步时test head 我得到以下输出 正如您所看到的 数据帧是一个切片 因此索引超出范围 我想做的是重置该数据帧的索引 所以我跑test rese
  • 如果字段值在外部列表中,Django 会注释布尔值

    想象一下我有这个 Django 模型 class Letter models Model name models CharField max length 1 unique True 还有这个列表 vowels a e i o u 我想查询
  • 如何从 Dockerfile 安装 Python 3.7 和 Pip

    我正在尝试构建基于 Ubuntu 18 04 的自定义 Docker 映像 Ubuntu 预装了 Python 3 6 但我想 1 安装 Python 3 7 2 将其设置为默认 Python 版本 这样就可以使用python代替pytho
  • azure 和 google 上的自定义联合代理

    azure 和 google 中的 aws 上的自定义联合代理可以替代什么 在AWS中 我可以创建一个允许联合用户登录并访问这样的资源的url https docs aws amazon com IAM latest UserGuide i
  • 如何在 Python 中将彩色输出打印到终端?

    是否有与 Perl 等效的 Python 语言 print color red print
  • 如何在python中检索aws批处理参数值?

    流程 Dynamo DB gt Lambda gt 批处理 如果将角色 arn 插入动态数据库 它是从 lambda 事件中检索的 然后使用submit job角色 arn 的 API 被传递为 parameters role arn ar
  • 获取列表中倒数第二个元素[重复]

    这个问题在这里已经有答案了 我可以通过以下方式获取列表的倒数第二个元素 gt gt gt lst a b c d e f gt gt gt print lst len lst 2 e 有没有比使用更好的方法print lst len lst
  • 如何在Python和Selenium中通过标签名称或id获取元素[重复]

    这个问题在这里已经有答案了 我正在尝试使用 Python 和 Selenium 获取输入 但它向我显示错误 我该如何解决这个错误 inputElement send keys getStock getStocklFunc 0 Error i
  • 在可编辑的QSqlQueryModel中实现setEditStrategy

    这是后续这个问题 https stackoverflow com questions 49752388 editable qtableview of complex sql query 在那里 我们创建了 QSqlQueryModel 的可
  • 将输入发送到 python 子进程而不等待结果

    我正在尝试为一段代码编写一些基本测试 该代码通常通过 stdin 无休止地接受输入 直到给出特定的退出命令 我想检查程序是否在给出一些输入字符串时崩溃 经过一段时间来考虑处理 但似乎无法弄清楚如何发送数据而不是陷入等待我不知道的输出关心 我
  • 导入错误:没有名为 google.auth 的模块

    当我尝试导入时firebase admin in python 2 7我收到错误 导入错误 没有名为 google auth 的模块 这是Docker文件 https github com ammaratef45 Attendance bl
  • 如何在sphinx中启用数学?

    我在用sphinx http sphinx pocoo org index html与pngmath http sphinx pocoo org ext math html module sphinx ext pngmath扩展来记录我的代
  • 如何禁止 celery 中的 pickle 序列化

    Celery 默认使用 pickle 作为任务的序列化方法 如中所述FAQ http ask github com celery faq html isn t using pickle a security concern 这代表一个安全漏
  • 尝试使用 AWS CLI 运行 ECS 任务时出现资源:内存错误

    我正在尝试使用 AWS ECS 和 docker 设置 CI 我使用 Codeship 作为 CI 工具 但这并不重要 我在 shell 脚本中执行以下步骤 使用我的 Dockerfile 构建镜像 将镜像推送到ECS存储库 将task d
  • Django South - 将 null=True 字段转换为 null=False 字段

    我的问题是 转变的最佳做法是什么null True场变成null False使用 Django South 的字段 具体来说 我正在与ForeignKey 你应该先写一个数据迁移 http south aeracode org docs t

随机推荐