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

Allow use of IAM Instance Roles #138

Closed
wants to merge 3 commits into from
Closed
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
16 changes: 16 additions & 0 deletions packages/docs-site/cypress/integration/instance-role.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/// <reference types="cypress" />

describe("Instance Role", () => {
if (Cypress.env("INSTANCE_ROLE_TEST")) {
it("should be able to upload a file and access it", () => {
cy.visit("/examples/instance-role");

cy.get("[data-test=file-input]").attachFile("woods.jpg");
cy.get("button")
.contains("Start upload")
.click();

cy.get("[data-test=image]").isFixtureImage("woods.jpg");
});
}
});
5 changes: 5 additions & 0 deletions packages/docs-site/src/pages/api/instance-role.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { APIRoute } from "next-s3-upload";

export default APIRoute.configure({
useInstanceRole: true
});
14 changes: 14 additions & 0 deletions packages/docs-site/src/pages/bucket-config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ export default APIRoute.configure({

Remember to always store your `accessKeyId` and `secretAccessKey` in environment variables. It's a security risk to check-in the plain text version of these keys when using version control. Environment variables work best because they keep the actual values out of commit history.

## Using IAM EC2 Instance Roles/Profiles

If you are using [IAM Instance Roles on an EC2 instance](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2.html), and wish to use the instance role to access S3, pass the following custom configuration object. The library will automatically attempt to connect using the role assigned to the EC2 instance.

```js
// pages/api/s3-upload.js
import { APIRoute } from "next-s3-upload";

export default APIRoute.configure({
bucket: "bucket-name",
useInstanceRole: true
});
```

## Bucket endpoint

You can also set the bucket endpoint as well as the path style using the API route. This is only needed if you are using a [non-AWS host](/other-providers).
Expand Down
46 changes: 46 additions & 0 deletions packages/docs-site/src/pages/examples/instance-role.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useS3Upload } from "next-s3-upload";
import { useState } from "react";

export default function UploadTest() {
let [imageUrl, setImageUrl] = useState();
let { FileInput, openFileDialog, uploadToS3 } = useS3Upload();

let handleFileChange = async file => {
let { url } = await uploadToS3(file, {
endpoint: {
request: {
url: "/api/instance-role"
}
}
});
setImageUrl(url);

let printUrl = url.replace(/^https:\/\//, "https:‎//");

console.log(
`%cSuccessfully uploaded to S3!`,
"background: #15803d; color: white; padding: 8px 12px"
);
console.log(
`%c${printUrl}`,
"background: #4f46e5; color: white; padding: 8px 12px"
);
};

return (
<div className="p-6 flex flex-col h-screen">
<FileInput onChange={handleFileChange} />
<div>
<button
className="bg-indigo-600 text-white rounded px-3 py-2 text-base font-medium shadow-sm"
onClick={openFileDialog}
>
Upload file
</button>
</div>
<div className="pt-8 flex-1 overflow-hidden flex">
{imageUrl && <img className="object-contain" src={imageUrl} />}
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion packages/docs-site/src/pages/setup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ yarn add next-s3-upload

## Environment variables

You'll need to setup the following environment variables for this package to work correctly.
You'll need to setup the following environment variables for this package to work correctly, unless you are using the [IAM EC2 Instance Roles option](/bucket-config) in which case the S3_UPLOAD_KEY, S3_UPLOAD_SECRET and S3_UPLOAD_REGION keys are not required.

```bash
S3_UPLOAD_KEY=AAAAAAAAAAAAAAAAAAAA
Expand Down
35 changes: 26 additions & 9 deletions packages/next-s3-upload/src/pages/api/s3-upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ let makeRouteHandler = (options: Options = {}): Handler => {
region: options.region,
forcePathStyle: options.forcePathStyle,
endpoint: options.endpoint,
useInstanceRole: options.useInstanceRole,
});

let missing = missingEnvs(config);
Expand All @@ -45,7 +46,7 @@ let makeRouteHandler = (options: Options = {}): Handler => {
let key = options.key
? await Promise.resolve(options.key(req, filename))
: `next-s3-uploads/${uuid()}/${sanitizeKey(filename)}`;
let { bucket, region, endpoint } = config;
let { bucket, region, endpoint, useInstanceRole } = config;

if (uploadType === 'presigned') {
let filetype = req.body.filetype;
Expand All @@ -69,13 +70,23 @@ let makeRouteHandler = (options: Options = {}): Handler => {
url,
});
} else {
let stsConfig: STSClientConfig = {
credentials: {
accessKeyId: config.accessKeyId,
secretAccessKey: config.secretAccessKey,
},
region,
};

let stsConfig: STSClientConfig = {};

if (!useInstanceRole) {
Object.assign(stsConfig, {
credentials: {
accessKeyId: config.accessKeyId,
secretAccessKey: config.secretAccessKey,
},
})
}

if (region) {
Object.assign(stsConfig, {
region
})
}

let policy = {
Statement: [
Expand Down Expand Up @@ -114,7 +125,13 @@ let makeRouteHandler = (options: Options = {}): Handler => {
};

let missingEnvs = (config: Record<string, any>): string[] => {
let required = ['accessKeyId', 'secretAccessKey', 'bucket', 'region'];
let required;

if (config.useInstanceRole) {
required = ['bucket'];
} else {
required = ['accessKeyId', 'secretAccessKey', 'bucket', 'region'];
}

return required.filter(key => !config[key] || config.key === '');
};
Expand Down
25 changes: 17 additions & 8 deletions packages/next-s3-upload/src/utils/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,24 @@ import { getConfig, S3Config } from './config';
export function getClient(s3Config?: S3Config) {
let config = getConfig(s3Config);

let client = new S3Client({
credentials: {
accessKeyId: config.accessKeyId,
secretAccessKey: config.secretAccessKey,
},
region: config.region,
let clientConfig = {
...(config.region ? { region: config.region } : {}),
...(config.forcePathStyle ? { forcePathStyle: config.forcePathStyle } : {}),
...(config.endpoint ? { endpoint: config.endpoint } : {}),
});
}

if (!config.useInstanceRole) {
Object.assign(clientConfig,
{
credentials: {
accessKeyId: config.accessKeyId,
secretAccessKey: config.secretAccessKey,
}
}
);
}

let client = new S3Client(clientConfig);

return client;
}
}
10 changes: 6 additions & 4 deletions packages/next-s3-upload/src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ export type S3Config = {
region?: string;
endpoint?: string;
forcePathStyle?: boolean;
useInstanceRole?: boolean;
};

export function getConfig(s3Config?: S3Config) {
return {
accessKeyId: s3Config?.accessKeyId ?? `${process.env.S3_UPLOAD_KEY}`,
accessKeyId: s3Config?.accessKeyId ?? process.env.S3_UPLOAD_KEY,
secretAccessKey:
s3Config?.secretAccessKey ?? `${process.env.S3_UPLOAD_SECRET}`,
bucket: s3Config?.bucket ?? `${process.env.S3_UPLOAD_BUCKET}`,
region: s3Config?.region ?? `${process.env.S3_UPLOAD_REGION}`,
s3Config?.secretAccessKey ?? process.env.S3_UPLOAD_SECRET,
bucket: s3Config?.bucket ?? process.env.S3_UPLOAD_BUCKET,
region: s3Config?.region ?? process.env.S3_UPLOAD_REGION,
endpoint: s3Config?.endpoint,
forcePathStyle: s3Config?.forcePathStyle,
useInstanceRole: s3Config?.useInstanceRole ?? false,
};
}