Skip to content

Commit

Permalink
add microsoft graph data source (blackstork-io#242)
Browse files Browse the repository at this point in the history
  • Loading branch information
anasmuhmd authored Sep 24, 2024
1 parent 5a97615 commit cf3c0d1
Show file tree
Hide file tree
Showing 16 changed files with 792 additions and 6 deletions.
1 change: 1 addition & 0 deletions .mockery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ packages:
config:
interfaces:
AzureOpenaiClient:
MicrosoftGraphClient:
github.com/blackstork-io/fabric/plugin/resolver:
config:
inpackage: true
Expand Down
105 changes: 105 additions & 0 deletions docs/plugins/microsoft/data-sources/microsoft_graph.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
title: "`microsoft_graph` data source"
plugin:
name: blackstork/microsoft
description: "The `microsoft_graph` data source queries Microsoft Graph"
tags: []
version: "v0.4.2"
source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/microsoft/"
resource:
type: data-source
type: docs
---

{{< breadcrumbs 2 >}}

{{< plugin-resource-header "blackstork/microsoft" "microsoft" "v0.4.2" "microsoft_graph" "data source" >}}

## Description
The `microsoft_graph` data source queries Microsoft Graph.

## Installation

To use `microsoft_graph` data source, you must install the plugin `blackstork/microsoft`.

To install the plugin, add the full plugin name to the `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), as shown below:

```hcl
fabric {
plugin_versions = {
"blackstork/microsoft" = ">= v0.4.2"
}
}
```

Note the version constraint set for the plugin.

## Configuration

The data source supports the following configuration arguments:

```hcl
config data microsoft_graph {
# The Azure client ID
#
# Required string.
# For example:
client_id = "some string"
# The Azure client secret. Required if private_key_file/privat_key/cert_thumbprint is not provided.
#
# Optional string.
# Default value:
client_secret = null
# The Azure tenant ID
#
# Required string.
# For example:
tenant_id = "some string"
# The path to the private key file. Ignored if private_key/client_secret is provided.
#
# Optional string.
# Default value:
private_key_file = null
# The private key contents. Ignored if client_secret is provided.
#
# Optional string.
# Default value:
private_key = null
# The key passphrase. Ignored if client_secret is provided.
#
# Optional string.
# Default value:
key_passphrase = null
}
```

## Usage

The data source supports the following execution arguments:

```hcl
data microsoft_graph {
# The API version
#
# Optional string.
# Default value:
api_version = "beta"
# The endpoint to query
#
# Required string.
# For example:
endpoint = "/security/incidents"
# The query parameters
#
# Optional map of string.
# Default value:
query_params = null
}
```
17 changes: 17 additions & 0 deletions docs/plugins/plugins.json
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,23 @@
"top_p"
]
},
{
"name": "microsoft_graph",
"type": "data-source",
"config_params": [
"client_id",
"client_secret",
"key_passphrase",
"private_key",
"private_key_file",
"tenant_id"
],
"arguments": [
"api_version",
"endpoint",
"query_params"
]
},
{
"name": "microsoft_sentinel_incidents",
"type": "data-source",
Expand Down
44 changes: 44 additions & 0 deletions examples/templates/microsoft/graph_data_source.fabric
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
fabric {
plugin_versions = {
"blackstork/microsoft" = ">= 0.4 < 1.0 || 0.4.0-rev0"
}
}

document "example" {
meta {
name = "example_document"
}

data microsoft_graph "mygraph" {
config {
client_id = ""
client_secret = ""
tenant_id = ""
# private_key_file = "<path-to-key-file>"
}
api_version = "v1.0"
endpoint = "/security/incidents"
query_params = {
"$top" = "10"
}
}

title = "List of Security Incidents"

content table {
rows = query_jq(".data.microsoft_graph.mygraph.value")
columns = [
{
"header" = "Severity"
"value" = "{{.row.value.severity}}"
},
{
"header" = "Display Name"
"value" = "{{.row.value.displayName}}"
}
]
}


}

3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.22
require (
github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai v0.6.0
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2
github.com/Masterminds/semver/v3 v3.2.1
github.com/Masterminds/sprig/v3 v3.2.3
github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2
Expand Down Expand Up @@ -89,6 +90,7 @@ require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-swiss/fonts v0.0.0-20221219152310-0b267088f53d // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.6.0 // indirect
Expand All @@ -102,6 +104,7 @@ require (
github.com/jellydator/ttlcache/v3 v3.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
Expand Down
20 changes: 20 additions & 0 deletions internal/microsoft/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"
"time"

"github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"
"github.com/google/go-querystring/query"
)

Expand All @@ -18,6 +19,8 @@ const (
version = "2023-11-01"
)

var scopes = []string{"https://graph.microsoft.com/.default"}

func String(s string) *string {
return &s
}
Expand Down Expand Up @@ -113,6 +116,23 @@ func (c *client) GetClientCredentialsToken(ctx context.Context, req *GetClientCr
return &data, nil
}

func AcquireToken(ctx context.Context, tenantId string, clientId string, cred confidential.Credential) (accessToken string, err error) {
confidentialClient, err := confidential.New(authURL+"/"+tenantId, clientId, cred)
if err != nil {
return
}
result, err := confidentialClient.AcquireTokenSilent(ctx, scopes)
if err != nil {
// cache miss, authenticate with another AcquireToken... method
result, err = confidentialClient.AcquireTokenByCredential(ctx, scopes)
if err != nil {
return
}
}
accessToken = result.AccessToken
return
}

func (c *client) ListIncidents(ctx context.Context, req *ListIncidentsReq) (*ListIncidentsRes, error) {
format := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.OperationalInsights/workspaces/%s/providers/Microsoft.SecurityInsights/incidents"
u, err := url.Parse(c.url + fmt.Sprintf(format, req.SubscriptionID, req.ResourceGroupName, req.WorkspaceName))
Expand Down
57 changes: 57 additions & 0 deletions internal/microsoft/client/graph_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package client

import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
)

const graphUrl = "https://graph.microsoft.com"

type graphClient struct {
accessToken string
apiVersion string
client *http.Client
}

func NewGraphClient(accessToken string, apiVersion string) *graphClient {
return &graphClient{
accessToken: accessToken,
apiVersion: apiVersion,
client: &http.Client{},
}
}

func (cli *graphClient) prepare(r *http.Request) {
r.Header.Set("Authorization", fmt.Sprintf("Bearer %s", cli.accessToken))
}

func (cli *graphClient) QueryGraph(ctx context.Context, endpoint string, queryParams url.Values) (result interface{}, err error) {
requestUrl, err := url.Parse(graphUrl + fmt.Sprintf("/%s%s", cli.apiVersion, endpoint))
if err != nil {
return
}
if queryParams != nil {
requestUrl.RawQuery = queryParams.Encode()
}
r, err := http.NewRequestWithContext(ctx, http.MethodGet, requestUrl.String(), nil)
if err != nil {
return
}
cli.prepare(r)
res, err := cli.client.Do(r)
if err != nil {
return
}
if res.StatusCode != http.StatusOK {
err = fmt.Errorf("microsoft graph client returned status code: %d", res.StatusCode)
return
}
defer res.Body.Close()
if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
return nil, err
}
return
}
3 changes: 2 additions & 1 deletion internal/microsoft/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package main

import (
"github.com/blackstork-io/fabric/internal/microsoft"
"github.com/blackstork-io/fabric/internal/microsoft/client"
pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
)

var version string

func main() {
pluginapiv1.Serve(
microsoft.Plugin(version, microsoft.DefaultClientLoader, microsoft.DefaultAzureOpenAIClientLoader),
microsoft.Plugin(version, microsoft.DefaultClientLoader, microsoft.DefaultAzureOpenAIClientLoader, microsoft.MakeDefaultMicrosoftGraphClientLoader(client.AcquireToken)),
)
}
2 changes: 1 addition & 1 deletion internal/microsoft/content_azure_openai_text_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TestAzureOpenAITextContentSuite(t *testing.T) {
func (s *AzureOpenAITextContentTestSuite) SetupSuite() {
s.plugin = microsoft.Plugin("1.0.0", nil, (func(apiKey string, endPoint string) (cli microsoft.AzureOpenaiClient, err error) {
return s.cli, nil
}))
}), nil)
s.schema = s.plugin.ContentProviders["azure_openai_text"]
}

Expand Down
Loading

0 comments on commit cf3c0d1

Please sign in to comment.