1
-
2
- --
3
- -- s3_get
1
+ --
2
+ -- s3_request
4
3
--
5
4
-- Installation:
6
5
--
7
6
-- CREATE EXTENSION pgcrypto;
8
7
-- CREATE EXTENSION http;
9
8
--
10
9
-- Utility function to take S3 object and access keys and create
11
- -- a signed HTTP GET request using the AWS4 signing scheme.
10
+ -- a signed HTTP request using the AWS4 signing scheme.
12
11
-- https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
13
12
--
14
13
-- Various pieces of the request are gathered into strings bundled together
18
17
--
19
18
-- https://cleverelephant-west-1.s3.amazonaws.com/META.json
20
19
--
21
- -- SELECT * FROM s3_get(
20
+ -- SELECT * FROM s3_request(
21
+ -- 'your_s3_access_key', -- access
22
+ -- 'your_s3_secret_key', -- secret
23
+ -- 'us-west-1', -- region
24
+ -- 'cleverelephant-west-1', -- bucket
25
+ -- 'META.json', -- object name
26
+ -- );
27
+ --
28
+ --
29
+ -- Created and delete objects too!
30
+ --
31
+ -- SELECT * FROM s3_request(
32
+ -- 'your_s3_access_key', -- access
33
+ -- 'your_s3_secret_key', -- secret
34
+ -- 'us-west-1', -- region
35
+ -- 'cleverelephant-west-1', -- bucket
36
+ -- 'testfile.txt', -- object name
37
+ -- 'PUT', -- http method
38
+ -- 'this is a test' -- payload
39
+ -- 'text/plain' -- payload mime type
40
+ -- );
41
+ --
42
+ -- SELECT * FROM s3_request(
43
+ -- 'your_s3_access_key', -- access
44
+ -- 'your_s3_secret_key', -- secret
45
+ -- 'us-west-1', -- region
46
+ -- 'cleverelephant-west-1', -- bucket
47
+ -- 'testfile.txt', -- object name
48
+ -- );
49
+ --
50
+ -- SELECT * FROM s3_request(
22
51
-- 'your_s3_access_key', -- access
23
52
-- 'your_s3_secret_key', -- secret
24
53
-- 'us-west-1', -- region
25
54
-- 'cleverelephant-west-1', -- bucket
26
- -- 'META.json' -- object
55
+ -- 'testfile.txt', -- object name
56
+ -- 'DELETE' -- http method
27
57
-- );
28
58
--
29
- CREATE OR REPLACE FUNCTION s3_get (
59
+
60
+ CREATE OR REPLACE FUNCTION s3_request (
30
61
access_key TEXT ,
31
62
secret_key TEXT ,
32
63
region TEXT ,
33
64
bucket TEXT ,
34
- object_key TEXT
65
+ object_key TEXT ,
66
+ http_method TEXT DEFAULT ' GET' ,
67
+ object_payload TEXT DEFAULT NULL ,
68
+ object_mimetype TEXT DEFAULT ' text/plain'
35
69
) RETURNS http_response AS $$
36
70
DECLARE
37
- http_method TEXT := ' GET' ;
38
71
host TEXT := bucket || ' .s3.' || region || ' .amazonaws.com' ;
39
72
endpoint TEXT := ' https://' || host || ' /' || object_key;
40
73
canonical_uri TEXT := ' /' || object_key;
@@ -45,7 +78,9 @@ DECLARE
45
78
now TIMESTAMP := now() AT TIME ZONE ' UTC' ;
46
79
amz_date TEXT := to_char(now, ' YYYYMMDD"T"HH24MISS"Z"' );
47
80
date_stamp TEXT := to_char(now, ' YYYYMMDD' );
48
- empty_payload_hash TEXT := ' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' ;
81
+
82
+ -- Must use this magic hash if the payload is empty
83
+ payload_hash TEXT := ' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' ;
49
84
50
85
canonical_headers TEXT ;
51
86
canonical_request TEXT ;
@@ -62,18 +97,33 @@ DECLARE
62
97
request http_request;
63
98
BEGIN
64
99
100
+ http_method := upper (http_method);
101
+
102
+ IF object_payload IS NOT NULL
103
+ THEN
104
+ payload_hash := encode(digest(convert_to(object_payload, ' UTF8' ), ' sha256' ), ' hex' );
105
+ END IF;
106
+
65
107
-- Construct the canonical headers
66
108
canonical_headers := ' host:' || host || E' \n '
67
- || ' x-amz-content-sha256:' || empty_payload_hash || E' \n '
109
+ || ' x-amz-content-sha256:' || payload_hash || E' \n '
68
110
|| ' x-amz-date:' || amz_date || E' \n ' ;
69
111
112
+ -- Signed headers must be in alphabetical order
113
+ -- so content-type goes first
114
+ IF object_payload IS NOT NULL
115
+ THEN
116
+ canonical_headers := ' content-type:' || lower (object_mimetype) || E' \n ' || canonical_headers;
117
+ signed_headers := ' content-type;' || signed_headers;
118
+ END IF;
119
+
70
120
-- Create the canonical request
71
121
canonical_request := http_method || E' \n ' ||
72
122
canonical_uri || E' \n ' ||
73
123
canonical_querystring || E' \n ' ||
74
124
canonical_headers || E' \n ' ||
75
125
signed_headers || E' \n ' ||
76
- empty_payload_hash ;
126
+ payload_hash ;
77
127
78
128
-- Define the credential scope
79
129
credential_scope := date_stamp || ' /' || region || ' /' || service || ' /aws4_request' ;
@@ -108,29 +158,27 @@ BEGIN
108
158
109
159
-- Perform the HTTP request
110
160
request := (
111
- ' GET ' ,
112
- endpoint,
113
- http_headers(' Authorization' , authorization_header,
114
- ' x-amz-content-sha256' , empty_payload_hash ,
115
- ' x-amz-date' , amz_date,
116
- ' host' , host),
117
- NULL ,
118
- NULL
161
+ http_method ,
162
+ endpoint,
163
+ http_headers(' Authorization' , authorization_header,
164
+ ' x-amz-content-sha256' , payload_hash ,
165
+ ' x-amz-date' , amz_date,
166
+ ' host' , host),
167
+ object_mimetype ,
168
+ object_payload
119
169
)::http_request;
120
170
121
171
-- Getting the canonical request and payload strings perfectly
122
172
-- formatted is an important step so debugging here in case
123
173
-- S3 rejects signed request
124
- RAISE DEBUG ' s3_get, canonical_request: %' , canonical_request;
125
- RAISE DEBUG ' s3_get, string_to_sign: %' , string_to_sign;
126
- RAISE DEBUG ' s3_get, request %' , request;
174
+ RAISE DEBUG ' s3_request, payload_hash: %' , payload_hash;
175
+ RAISE DEBUG ' s3_request, canonical_request: %' , canonical_request;
176
+ RAISE DEBUG ' s3_request, string_to_sign: %' , string_to_sign;
177
+ RAISE DEBUG ' s3_request, request %' , request;
127
178
128
179
RETURN http(request);
180
+ -- RETURN NULL;
129
181
130
182
END;
131
183
$$ LANGUAGE ' plpgsql'
132
184
VOLATILE;
133
-
134
-
135
-
136
-
0 commit comments