Skip to content

Commit 0eba4c2

Browse files
Cheruvu.Sahithee.Vaibhav@ibm.comCheruvu.Sahithee.Vaibhav@ibm.com
authored andcommitted
out_azure_blob: add service principal auth
Signed-off-by: Cheruvu.Sahithee.Vaibhav@ibm.com <Cheruvu.Sahithee.Vaibhav@ibm.com>
1 parent f6126eb commit 0eba4c2

4 files changed

Lines changed: 237 additions & 3 deletions

File tree

plugins/out_azure_blob/azure_blob.c

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1372,6 +1372,7 @@ static int ensure_container(struct flb_azure_blob *ctx)
13721372
static int cb_azure_blob_init(struct flb_output_instance *ins,
13731373
struct flb_config *config, void *data)
13741374
{
1375+
int ret;
13751376
struct flb_azure_blob *ctx = NULL;
13761377
(void) ins;
13771378
(void) config;
@@ -1384,8 +1385,9 @@ static int cb_azure_blob_init(struct flb_output_instance *ins,
13841385
return -1;
13851386
}
13861387

1388+
ctx->ins = ins;
1389+
13871390
if (ctx->buffering_enabled == FLB_TRUE) {
1388-
ctx->ins = ins;
13891391
ctx->retry_time = 0;
13901392

13911393
/* Initialize local storage */
@@ -1415,6 +1417,36 @@ static int cb_azure_blob_init(struct flb_output_instance *ins,
14151417
flb_plg_info(ctx->ins, "Using upload size %lu bytes", ctx->file_size);
14161418
}
14171419

1420+
/* Initialize OAuth2 context for service principal auth */
1421+
if (ctx->atype == AZURE_BLOB_AUTH_SERVICE_PRINCIPAL) {
1422+
pthread_mutex_init(&ctx->token_mutex, NULL);
1423+
flb_sds_t token_url;
1424+
1425+
token_url = flb_sds_create_size(256);
1426+
if (!token_url) {
1427+
flb_plg_error(ctx->ins, "failed to allocate token URL");
1428+
return -1;
1429+
}
1430+
1431+
ret = flb_sds_snprintf(&token_url, flb_sds_alloc(token_url),
1432+
"%s/%s/oauth2/v2.0/token",
1433+
AZURE_BLOB_DEFAULT_AUTHORITY_HOST, ctx->tenant_id);
1434+
if (ret < 0) {
1435+
flb_plg_error(ctx->ins, "failed to build token URL");
1436+
flb_sds_destroy(token_url);
1437+
return -1;
1438+
}
1439+
1440+
ctx->o = flb_oauth2_create(ctx->config, token_url, AZURE_BLOB_TOKEN_REFRESH);
1441+
flb_sds_destroy(token_url);
1442+
1443+
if (!ctx->o) {
1444+
flb_plg_error(ctx->ins, "failed to create OAuth2 context");
1445+
pthread_mutex_destroy(&ctx->token_mutex);
1446+
return -1;
1447+
}
1448+
}
1449+
14181450
flb_output_set_context(ins, ctx);
14191451

14201452
flb_output_set_http_debug_callbacks(ins);
@@ -2390,6 +2422,11 @@ static int cb_azure_blob_exit(void *data, struct flb_config *config)
23902422
ctx->u = NULL;
23912423
}
23922424

2425+
/* Destroy token mutex for service principal auth */
2426+
if (ctx->atype == AZURE_BLOB_AUTH_SERVICE_PRINCIPAL) {
2427+
pthread_mutex_destroy(&ctx->token_mutex);
2428+
}
2429+
23932430
flb_azure_blob_conf_destroy(ctx);
23942431
return 0;
23952432
}
@@ -2514,7 +2551,7 @@ static struct flb_config_map config_map[] = {
25142551
{
25152552
FLB_CONFIG_MAP_STR, "auth_type", "key",
25162553
0, FLB_TRUE, offsetof(struct flb_azure_blob, auth_type),
2517-
"Set the auth type: key or sas"
2554+
"Set the auth type: key, sas, or service_principal"
25182555
},
25192556

25202557
{
@@ -2523,6 +2560,24 @@ static struct flb_config_map config_map[] = {
25232560
"Azure Blob SAS token"
25242561
},
25252562

2563+
{
2564+
FLB_CONFIG_MAP_STR, "tenant_id", NULL,
2565+
0, FLB_TRUE, offsetof(struct flb_azure_blob, tenant_id),
2566+
"Azure AD tenant ID (required for service_principal auth)"
2567+
},
2568+
2569+
{
2570+
FLB_CONFIG_MAP_STR, "client_id", NULL,
2571+
0, FLB_TRUE, offsetof(struct flb_azure_blob, client_id),
2572+
"Azure AD client ID (required for service_principal auth)"
2573+
},
2574+
2575+
{
2576+
FLB_CONFIG_MAP_STR, "client_secret", NULL,
2577+
0, FLB_TRUE, offsetof(struct flb_azure_blob, client_secret),
2578+
"Azure AD client secret (required for service_principal auth)"
2579+
},
2580+
25262581
{
25272582
FLB_CONFIG_MAP_STR, "database_file", NULL,
25282583
0, FLB_TRUE, offsetof(struct flb_azure_blob, database_file),

plugins/out_azure_blob/azure_blob.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <fluent-bit/flb_sds.h>
2626
#include <fluent-bit/flb_sqldb.h>
2727
#include <fluent-bit/flb_time.h>
28+
#include <fluent-bit/flb_oauth2.h>
2829

2930
/* Content-Type */
3031
#define AZURE_BLOB_CT "Content-Type"
@@ -53,6 +54,12 @@
5354

5455
#define AZURE_BLOB_AUTH_KEY 0
5556
#define AZURE_BLOB_AUTH_SAS 1
57+
#define AZURE_BLOB_AUTH_SERVICE_PRINCIPAL 2
58+
59+
/* OAuth2 defaults for service principal authentication */
60+
#define AZURE_BLOB_DEFAULT_AUTHORITY_HOST "https://login.microsoftonline.com"
61+
#define AZURE_BLOB_OAUTH_SCOPE "https://storage.azure.com/.default"
62+
#define AZURE_BLOB_TOKEN_REFRESH 3000 /* refresh token every 50 minutes */
5663

5764
struct flb_azure_blob {
5865
int auto_create_container;
@@ -69,6 +76,11 @@ struct flb_azure_blob {
6976
flb_sds_t date_key;
7077
flb_sds_t auth_type;
7178
flb_sds_t sas_token;
79+
80+
/* Service Principal authentication fields */
81+
flb_sds_t tenant_id;
82+
flb_sds_t client_id;
83+
flb_sds_t client_secret;
7284
flb_sds_t database_file;
7385
size_t part_size;
7486
time_t upload_parts_timeout;
@@ -125,6 +137,10 @@ struct flb_azure_blob {
125137
unsigned char *decoded_sk; /* decoded shared key */
126138
size_t decoded_sk_size; /* size of decoded shared key */
127139

140+
/* Service Principal OAuth2 context */
141+
struct flb_oauth2 *o;
142+
pthread_mutex_t token_mutex;
143+
128144
#ifdef FLB_HAVE_SQLDB
129145
/*
130146
* SQLite by default is not built with multi-threading enabled, and

plugins/out_azure_blob/azure_blob_conf.c

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <fluent-bit/flb_base64.h>
2222
#include <fluent-bit/flb_pack.h>
2323
#include <fluent-bit/flb_compression.h>
24+
#include <fluent-bit/flb_oauth2.h>
2425

2526
#include "azure_blob.h"
2627
#include "azure_blob_conf.h"
@@ -616,11 +617,16 @@ struct flb_azure_blob *flb_azure_blob_conf_create(struct flb_output_instance *in
616617
else if (strcasecmp(tmp, "sas") == 0) {
617618
ctx->atype = AZURE_BLOB_AUTH_SAS;
618619
}
620+
else if (strcasecmp(tmp, "service_principal") == 0) {
621+
ctx->atype = AZURE_BLOB_AUTH_SERVICE_PRINCIPAL;
622+
}
619623
else {
620624
flb_plg_error(ctx->ins, "invalid auth_type value '%s'", tmp);
621625
return NULL;
622626
}
623627
}
628+
629+
/* Validate auth-specific required fields */
624630
if (ctx->atype == AZURE_BLOB_AUTH_KEY &&
625631
ctx->shared_key == NULL) {
626632
flb_plg_error(ctx->ins, "'shared_key' has not been set");
@@ -637,6 +643,21 @@ struct flb_azure_blob *flb_azure_blob_conf_create(struct flb_output_instance *in
637643
}
638644
}
639645

646+
if (ctx->atype == AZURE_BLOB_AUTH_SERVICE_PRINCIPAL) {
647+
if (ctx->tenant_id == NULL) {
648+
flb_plg_error(ctx->ins, "'tenant_id' is required for service_principal auth");
649+
return NULL;
650+
}
651+
if (ctx->client_id == NULL) {
652+
flb_plg_error(ctx->ins, "'client_id' is required for service_principal auth");
653+
return NULL;
654+
}
655+
if (ctx->client_secret == NULL) {
656+
flb_plg_error(ctx->ins, "'client_secret' is required for service_principal auth");
657+
return NULL;
658+
}
659+
}
660+
640661
/* If the shared key is set decode it */
641662
if (ctx->atype == AZURE_BLOB_AUTH_KEY &&
642663
ctx->shared_key != NULL) {
@@ -696,6 +717,17 @@ struct flb_azure_blob *flb_azure_blob_conf_create(struct flb_output_instance *in
696717
return NULL;
697718
}
698719

720+
/*
721+
* Service principal authentication requires TLS for Azure Storage API
722+
* requests. Validate that TLS is explicitly enabled.
723+
*/
724+
if (ctx->atype == AZURE_BLOB_AUTH_SERVICE_PRINCIPAL &&
725+
ins->use_tls != FLB_TRUE) {
726+
flb_plg_error(ctx->ins,
727+
"service_principal auth requires TLS; set 'tls On'");
728+
return NULL;
729+
}
730+
699731
/*
700732
* Setting up the real endpoint:
701733
*
@@ -795,7 +827,8 @@ struct flb_azure_blob *flb_azure_blob_conf_create(struct flb_output_instance *in
795827
ctx->btype == AZURE_BLOB_APPENDBLOB ? "appendblob" : "blockblob",
796828
ctx->emulator_mode ? "yes" : "no",
797829
ctx->real_endpoint ? ctx->real_endpoint : "no",
798-
ctx->atype == AZURE_BLOB_AUTH_KEY ? "key" : "sas");
830+
ctx->atype == AZURE_BLOB_AUTH_KEY ? "key" :
831+
(ctx->atype == AZURE_BLOB_AUTH_SAS ? "sas" : "service_principal"));
799832
return ctx;
800833
}
801834

@@ -840,6 +873,14 @@ void flb_azure_blob_conf_destroy(struct flb_azure_blob *ctx)
840873
flb_sds_destroy(ctx->shared_key_prefix);
841874
}
842875

876+
/* Cleanup service principal resources */
877+
if (ctx->atype == AZURE_BLOB_AUTH_SERVICE_PRINCIPAL) {
878+
if (ctx->o) {
879+
flb_oauth2_destroy(ctx->o);
880+
ctx->o = NULL;
881+
}
882+
}
883+
843884
if (ctx->u) {
844885
flb_upstream_destroy(ctx->u);
845886
}

plugins/out_azure_blob/azure_blob_http.c

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <fluent-bit/flb_hmac.h>
2525
#include <fluent-bit/flb_sds.h>
2626
#include <fluent-bit/flb_kv.h>
27+
#include <fluent-bit/flb_oauth2.h>
2728

2829
#include "azure_blob.h"
2930
#include "azure_blob_uri.h"
@@ -38,6 +39,111 @@ static int hmac_sha256_sign(unsigned char out[32],
3839
out, 32);
3940
}
4041

42+
static int azb_get_oauth2_token(struct flb_azure_blob *ctx)
43+
{
44+
int ret;
45+
char *token;
46+
47+
flb_oauth2_payload_clear(ctx->o);
48+
49+
ret = flb_oauth2_payload_append(ctx->o,
50+
"grant_type", 10,
51+
"client_credentials", 18);
52+
if (ret == -1) {
53+
flb_plg_error(ctx->ins, "failed to append OAuth2 grant type");
54+
return -1;
55+
}
56+
57+
ret = flb_oauth2_payload_append(ctx->o,
58+
"scope", 5,
59+
AZURE_BLOB_OAUTH_SCOPE, -1);
60+
if (ret == -1) {
61+
flb_plg_error(ctx->ins, "failed to append OAuth2 scope");
62+
return -1;
63+
}
64+
65+
ret = flb_oauth2_payload_append(ctx->o,
66+
"client_id", 9,
67+
ctx->client_id, -1);
68+
if (ret == -1) {
69+
flb_plg_error(ctx->ins, "failed to append OAuth2 client ID");
70+
return -1;
71+
}
72+
73+
ret = flb_oauth2_payload_append(ctx->o,
74+
"client_secret", 13,
75+
ctx->client_secret, -1);
76+
if (ret == -1) {
77+
flb_plg_error(ctx->ins, "failed to append OAuth2 client secret");
78+
return -1;
79+
}
80+
81+
token = flb_oauth2_token_get(ctx->o);
82+
if (!token) {
83+
flb_plg_error(ctx->ins, "failed to retrieve OAuth2 access token");
84+
return -1;
85+
}
86+
87+
return 0;
88+
}
89+
90+
/*
91+
* Get the current Azure Blob bearer token as a formatted string.
92+
* Acquires the token mutex, refreshes the token if expired, and returns
93+
* a copy of the token string in the format "<token_type> <access_token>".
94+
* The caller must destroy the returned flb_sds_t.
95+
*/
96+
static int azb_get_oauth2_authorization_header(struct flb_azure_blob *ctx,
97+
flb_sds_t *out_auth_header)
98+
{
99+
int ret;
100+
flb_sds_t auth_header = NULL;
101+
102+
if (!out_auth_header) {
103+
return -1;
104+
}
105+
106+
/* Acquire token mutex */
107+
ret = pthread_mutex_lock(&ctx->token_mutex);
108+
if (ret != 0) {
109+
flb_plg_error(ctx->ins, "failed to lock token mutex");
110+
return -1;
111+
}
112+
113+
/* Check if token needs refresh */
114+
if (flb_oauth2_token_expired(ctx->o) == FLB_TRUE) {
115+
ret = azb_get_oauth2_token(ctx);
116+
if (ret != 0) {
117+
pthread_mutex_unlock(&ctx->token_mutex);
118+
flb_plg_error(ctx->ins, "failed to refresh OAuth2 token");
119+
return -1;
120+
}
121+
}
122+
123+
/* Build Authorization header while holding lock */
124+
auth_header = flb_sds_create_size(flb_sds_len(ctx->o->token_type) +
125+
flb_sds_len(ctx->o->access_token) + 2);
126+
if (!auth_header) {
127+
pthread_mutex_unlock(&ctx->token_mutex);
128+
flb_plg_error(ctx->ins, "failed to allocate authorization header");
129+
return -1;
130+
}
131+
132+
ret = flb_sds_snprintf(&auth_header, flb_sds_alloc(auth_header),
133+
"%s %s", ctx->o->token_type, ctx->o->access_token);
134+
if (ret < 0) {
135+
flb_sds_destroy(auth_header);
136+
pthread_mutex_unlock(&ctx->token_mutex);
137+
flb_plg_error(ctx->ins, "failed to format authorization header");
138+
return -1;
139+
}
140+
141+
pthread_mutex_unlock(&ctx->token_mutex);
142+
143+
*out_auth_header = auth_header;
144+
return 0;
145+
}
146+
41147
static flb_sds_t canonical_headers(struct flb_http_client *c)
42148
{
43149
flb_sds_t ch;
@@ -300,6 +406,7 @@ int azb_http_client_setup(struct flb_azure_blob *ctx, struct flb_http_client *c,
300406
ssize_t content_length, int blob_type,
301407
int content_type, int content_encoding)
302408
{
409+
int ret;
303410
int len;
304411
time_t now;
305412
struct tm tm;
@@ -358,6 +465,7 @@ int azb_http_client_setup(struct flb_azure_blob *ctx, struct flb_http_client *c,
358465
/* Azure header: x-ms-version */
359466
flb_http_add_header(c, "x-ms-version", 12, "2019-12-12", 10);
360467

468+
361469
if (ctx->atype == AZURE_BLOB_AUTH_KEY) {
362470
can_req = azb_http_canonical_request(ctx, c, content_length, content_type,
363471
content_encoding);
@@ -374,6 +482,20 @@ int azb_http_client_setup(struct flb_azure_blob *ctx, struct flb_http_client *c,
374482
flb_sds_destroy(can_req);
375483
flb_sds_destroy(auth);
376484
}
485+
else if (ctx->atype == AZURE_BLOB_AUTH_SERVICE_PRINCIPAL) {
486+
/* Get OAuth2 authorization header (thread-safe) */
487+
ret = azb_get_oauth2_authorization_header(ctx, &auth);
488+
if (ret != 0) {
489+
flb_plg_error(ctx->ins, "failed to get OAuth2 authorization header");
490+
return -1;
491+
}
492+
493+
/* Azure header: authorization */
494+
flb_http_add_header(c, "Authorization", 13, auth, flb_sds_len(auth));
495+
496+
/* Release buffer */
497+
flb_sds_destroy(auth);
498+
}
377499

378500
/* Set callback context to the HTTP client context */
379501
flb_http_set_callback_context(c, ctx->ins->callback);

0 commit comments

Comments
 (0)