Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to read the license key from AWS secrets manager #18

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ packaged.yaml
node_modules
package.json
package-lock.json

.python-version
build/
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ python_version = "3.7"

[packages]
aiohttp = "*"
boto3 = "*"

[pipenv]
allow_prereleases = true
326 changes: 198 additions & 128 deletions Pipfile.lock

Large diffs are not rendered by default.

43 changes: 43 additions & 0 deletions build-zip.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash

# For those unable to use SAM, this builds a zip file that can be deployed using the AWS CLI or terraform or the like.
# The resulting zip will be placed in the build directory. It uses a python3 virtual environment, per the
# instructions here: https://docs.aws.amazon.com/lambda/latest/dg/python-package.html

# A suffix may be added to the zip name by defining the SUFFIX environment variable before invoking this script.

# Constants
BUILDDIR=build

# Extract the version from the template.yaml file
VERSION=`grep SemanticVersion template.yaml | sed -e 's/ *SemanticVersion: *//'`
ZIPFILE=newrelic-log-ingestion-${VERSION}${SUFFIX}.zip

# Clean up any old build
echo "Cleaning old builds..."
rm -rf ${BUILDDIR}

# Create the virtual environment, and activate it
echo "Setting up virtual environment..."
python3 -m venv ${BUILDDIR}
source build/bin/activate

# Install the requirements, excluding boto3, which is pre-installed in AWS
echo "Installing dependencies..."
cat src/requirements.txt | grep -v boto3 > /tmp/requirements.txt
pip3 --disable-pip-version-check install --requirement /tmp/requirements.txt

# Deactivate the virtual environment
deactivate

# Package up the dependencies
echo "Creating zip of dependencies..."
(cd ${BUILDDIR}/lib/python*/site-packages && zip --quiet --recurse-paths -9 ../../../${ZIPFILE} .)

# Add the function to the zip file
echo "Adding function to zip..."
(cd src && zip --grow --quiet ../${BUILDDIR}/${ZIPFILE} function.py)

# All done!
echo "Done, zipfile: ${BUILDDIR}/${ZIPFILE}"

29 changes: 27 additions & 2 deletions src/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
https://docs.newrelic.com/
"""

import boto3
import datetime
import gzip
import json
Expand Down Expand Up @@ -107,6 +108,9 @@ class EntryType(Enum):
LOGGING_LAMBDA_VERSION = "1.0.2"
LOGGING_PLUGIN_METADATA = {"type": "lambda", "version": LOGGING_LAMBDA_VERSION}

# Once the license key has been fetched, save it to avoid fetching again
cached_license_key = None


class MaxRetriesException(Exception):
pass
Expand Down Expand Up @@ -258,11 +262,32 @@ def _generate_payloads(data, split_function):

def _get_license_key(license_key=None):
"""
This functions gets New Relic's license key from env vars.
This functions gets New Relic's license key from env vars or AWS secrets manager.
"""
global cached_license_key
if license_key:
return license_key
return os.getenv("LICENSE_KEY", "")

if cached_license_key:
return cached_license_key

# If a secrets manager ARN was specified, use it to grab the license key, otherwise
# obtain it from the LICENSE_KEY environment variable.
secret_arn = os.getenv("SECRET_KEY_ARN", "")
if secret_arn:
secrets_client = boto3.client("secretsmanager")
secret = secrets_client.get_secret_value(SecretId=secret_arn)
cached_license_key = secret.get("SecretString")
if _debug_logging_enabled():
print(
"(got license key from secrets manager, len={})".format(
len(cached_license_key)
)
)
else:
cached_license_key = os.getenv("LICENSE_KEY", "")
Comment on lines +277 to +288
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use the LICENSE_KEY env var first. If it's set, we use it and are done. Otherwise, we fall back to secrets manager, which has considerable overhead.

In addition, we should move the secret manager fetch to the cold start period, rather than first invocation.

Finally, I suspect the global state you've introduced here is responsible for the test failures. You'll need to adjust the tests so that they don't have side effects, and aren't dependent on execution order.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MattWhelan You are correct, the global state makes the unit tests challenging. To address that and your other concerns, I'd like to change the code so that _get_license_key is called exactly once, from lambda_handler (which I believe is the "cold start period" to which you're referring).

That is a much more involved change, as the license key then needs to be passed down to everything that lambda_handler calls, but it seems to me like the right approach. Would you agree?


return cached_license_key


def _debug_logging_enabled():
Expand Down
21 changes: 14 additions & 7 deletions src/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
-i https://pypi.org/simple
aiohttp==4.0.0a1
async-timeout==3.0.1
attrs==19.3.0
aiohttp==3.6.2
async-timeout==3.0.1; python_full_version >= '3.5.3'
attrs==19.3.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
boto3==1.14.10
botocore==1.17.10
chardet==3.0.4
idna==2.9
multidict==4.7.5
typing-extensions==3.7.4.1
yarl==1.4.2
docutils==0.15.2; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
idna==2.9; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
jmespath==0.10.0; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
multidict==4.7.6; python_version >= '3.5'
python-dateutil==2.8.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
s3transfer==0.3.3
six==1.15.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
urllib3==1.25.9; python_version != '3.4'
yarl==1.4.2; python_version >= '3.5'