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

Update plugin to use github.com/microsoftgraph/msgraph-sdk-go #62

Merged
merged 22 commits into from
Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b10ef9f
Update plugin to use microsoft graph SDK
Subhajit97 Jul 18, 2022
0d4c83f
Update getTenant to read tenant from config and env variables
Subhajit97 Jul 19, 2022
6513f4c
Fix getTenant function logic
Subhajit97 Jul 19, 2022
48c0588
Update client certificate authentication and migrate resources as per…
Subhajit97 Jul 22, 2022
3911d92
Update error handling
Subhajit97 Jul 25, 2022
4d7e3b7
Update error handling and add missing columns
Subhajit97 Jul 26, 2022
ae04dcc
Add missing column from user
Subhajit97 Jul 27, 2022
3941c63
Migrated tables to new format and removed test tables
Subhajit97 Jul 27, 2022
6c88d1d
Update table example docs
Subhajit97 Jul 27, 2022
113b5d2
Resolved conflicts
Subhajit97 Jul 28, 2022
312ef99
Fix lint error
Subhajit97 Jul 28, 2022
b806330
reomve test tables
Subhajit97 Jul 28, 2022
bd9e7b4
Add missing columns for user, group and application table
Subhajit97 Aug 4, 2022
6f4bb4b
Fix linter error
Subhajit97 Aug 5, 2022
b255be1
Update azuread_identity_provider table to use msgraph SDK
Subhajit97 Aug 5, 2022
7651e13
Update error logs, remove unused functions and remove column refresh_…
Subhajit97 Aug 5, 2022
6c63c27
Rename helpers.go => transforms.go
Subhajit97 Aug 8, 2022
8bf0daf
Update group table to return [] for resourceBehaviorOptions and resou…
Subhajit97 Aug 9, 2022
595a997
update
Subhajit97 Aug 9, 2022
7b32f77
Fix lint error
Subhajit97 Aug 9, 2022
7241756
Bump timeout in golangci-lint to 15 minutes
cbruno10 Aug 9, 2022
3522d9e
Revert timeout in golangci-lint to 10 minutes
cbruno10 Aug 9, 2022
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
2 changes: 1 addition & 1 deletion .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ jobs:
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: latest
args: --timeout=10m
args: --timeout=10m
51 changes: 51 additions & 0 deletions azuread/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package azuread

import (
"context"
"encoding/json"
"strings"

"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
"github.com/turbot/steampipe-plugin-sdk/v3/plugin"
)

type RequestError struct {
Code string
Message string
}

func (m *RequestError) Error() string {
errStr, err := json.Marshal(m)
if err != nil {
return ""
}
return string(errStr)
}

func getErrorObject(err error) *RequestError {
if oDataError, ok := err.(*odataerrors.ODataError); ok {
if terr := oDataError.GetError(); terr != nil {
return &RequestError{
Code: *terr.GetCode(),
Message: *terr.GetMessage(),
}
}
}

return nil
}

func isIgnorableErrorPredicate(ignoreErrorCodes []string) plugin.ErrorPredicateWithContext {
return func(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData, err error) bool {
if err != nil {
if terr, ok := err.(*RequestError); ok {
for _, item := range ignoreErrorCodes {
if terr != nil && (terr.Code == item || strings.Contains(terr.Message, item)) {
return true
}
}
}
}
return false
}
}
4 changes: 3 additions & 1 deletion azuread/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ func Plugin(ctx context.Context) *plugin.Plugin {
Name: pluginName,
DefaultTransform: transform.FromCamel(),
DefaultGetConfig: &plugin.GetConfig{
ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Request_ResourceNotFound"}),
IgnoreConfig: &plugin.IgnoreConfig{
ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound"}),
},
},
ConnectionConfigSchema: &plugin.ConnectionConfigSchema{
NewInstance: ConfigInstance,
Expand Down
244 changes: 120 additions & 124 deletions azuread/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,183 +3,179 @@ package azuread
import (
"bytes"
"context"
"crypto"
"crypto/x509"
"encoding/json"
"fmt"
"log"
"os"
"os/exec"
"runtime"

"github.com/manicminer/hamilton/auth"
"github.com/manicminer/hamilton/environments"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
a "github.com/microsoft/kiota-authentication-azure-go"
msgraphsdkgo "github.com/microsoftgraph/msgraph-sdk-go"
"github.com/turbot/steampipe-plugin-sdk/v3/plugin"
)

// Session info
type Session struct {
TenantID string
Authorizer auth.Authorizer
}

/* GetNewSession creates an session configured from (~/.steampipe/config, environment variables and CLI) in the order:
/*
GetGraphClient creates a graph service client configured from (~/.steampipe/config, environment variables and CLI) in the order:
1. Client secret
2. Client certificate
3. Username and password
4. MSI
5. CLI
3. MSI
4. CLI
*/
func GetNewSession(ctx context.Context, d *plugin.QueryData) (sess *Session, err error) {
func GetGraphClient(ctx context.Context, d *plugin.QueryData) (*msgraphsdkgo.GraphServiceClient, *msgraphsdkgo.GraphRequestAdapter, error) {
logger := plugin.Logger(ctx)

// Have we already created and cached the session?
// Hamilton SDK already acquires a new token when expired, so don't handle here again
sessionCacheKey := "GetNewSession"
sessionCacheKey := "GetGraphClient"
if cachedData, ok := d.ConnectionManager.Cache.Get(sessionCacheKey); ok {
return cachedData.(*Session), nil
}

azureADConfig := GetConfig(d.Connection)
var tenantID string
authMethod, authConfig, err := getApplicableAuthorizationDetails(ctx, azureADConfig)
if err != nil {
logger.Debug("GetNewSession__", "getApplicableAuthorizationDetails error", err)
return nil, err
}

if authConfig.TenantID != "" {
tenantID = authConfig.TenantID
}

authorizer, err := authConfig.NewAuthorizer(ctx, auth.MsGraph)
if err != nil {
log.Fatal(err)
return cachedData.(*msgraphsdkgo.GraphServiceClient), nil, nil
}

if authMethod == "CLI" {
tenantID, err = getTenantFromCLI()
if err != nil {
logger.Debug("GetNewSession__", "getTenantFromCLI error", err)
return nil, err
}
}
var tenantID, environment, clientID, clientSecret, certificatePath, certificatePassword string

sess = &Session{
Authorizer: authorizer,
TenantID: tenantID,
}

// Save session into cache
d.ConnectionManager.Cache.Set(sessionCacheKey, sess)

return sess, err
}

func getApplicableAuthorizationDetails(ctx context.Context, config azureADConfig) (authMethod string, authConfig auth.Config, err error) {

var environment, tenantID, clientID, clientSecret, certificatePath, certificatePassword, msiEndpoint string
var enableMsi bool
// username, password string
if config.TenantID != nil {
tenantID = *config.TenantID
azureADConfig := GetConfig(d.Connection)
if azureADConfig.TenantID != nil {
tenantID = *azureADConfig.TenantID
} else {
tenantID = os.Getenv("AZURE_TENANT_ID")
}

if config.Environment != nil {
environment = *config.Environment
if azureADConfig.Environment != nil {
environment = *azureADConfig.Environment
} else {
environment = os.Getenv("AZURE_ENVIRONMENT")
}

// Can be "AZURECHINACLOUD", "AZUREGERMANCLOUD", "AZUREPUBLICCLOUD", "AZUREUSGOVERNMENTCLOUD"
switch environment {
case "AZURECHINACLOUD":
authConfig.Environment = environments.China
case "AZUREUSGOVERNMENTCLOUD":
authConfig.Environment = environments.USGovernmentL4
case "AZUREGERMANCLOUD":
authConfig.Environment = environments.Germany
default:
authConfig.Environment = environments.Global
var enableMsi bool
if azureADConfig.EnableMsi != nil {
enableMsi = *azureADConfig.EnableMsi
}

// 1. Client secret credentials
if config.ClientID != nil {
clientID = *config.ClientID
if azureADConfig.ClientID != nil {
clientID = *azureADConfig.ClientID
} else {
clientID = os.Getenv("AZURE_CLIENT_ID")
}

if config.ClientSecret != nil {
clientSecret = *config.ClientSecret
if azureADConfig.ClientSecret != nil {
clientSecret = *azureADConfig.ClientSecret
} else {
clientSecret = os.Getenv("AZURE_CLIENT_SECRET")
}

// 2. Client certificate credentials
if config.CertificatePath != nil {
certificatePath = *config.CertificatePath
if azureADConfig.CertificatePath != nil {
certificatePath = *azureADConfig.CertificatePath
} else {
certificatePath = os.Getenv("AZURE_CERTIFICATE_PATH")
}

if config.CertificatePassword != nil {
certificatePassword = *config.CertificatePassword
if azureADConfig.CertificatePassword != nil {
certificatePassword = *azureADConfig.CertificatePassword
} else {
certificatePassword = os.Getenv("AZURE_CERTIFICATE_PASSWORD")
}

// TODO
// 3. Username and password
// if config.Username != nil {
// username = *config.Username
// } else {
// username = os.Getenv("AZURE_USERNAME")
// }
var cloudConfiguration cloud.Configuration
switch environment {
case "AZURECHINACLOUD":
cloudConfiguration = cloud.AzureChina
case "AZUREUSGOVERNMENTCLOUD":
cloudConfiguration = cloud.AzureGovernment
default:
cloudConfiguration = cloud.AzurePublic
}

// if config.Password != nil {
// password = *config.Password
// } else {
// password = os.Getenv("AZURE_PASSWORD")
// }
var cred azcore.TokenCredential
var err error
if tenantID == "" { // CLI authentication
cred, err = azidentity.NewAzureCLICredential(
&azidentity.AzureCLICredentialOptions{},
)
if err != nil {
logger.Error("GetGraphClient", "cli_credential_error", err)
return nil, nil, err
}
} else if tenantID != "" && clientID != "" && clientSecret != "" { // Client secret authentication
cred, err = azidentity.NewClientSecretCredential(
tenantID,
clientID,
clientSecret,
&azidentity.ClientSecretCredentialOptions{
ClientOptions: policy.ClientOptions{
Cloud: cloudConfiguration,
},
},
)
if err != nil {
logger.Error("GetGraphClient", "client_secret_credential_error", err)
return nil, nil, err
}
} else if tenantID != "" && clientID != "" && certificatePath != "" { // Client certificate authentication
// Load certificate from given path
loadFile, err := os.ReadFile(certificatePath)
if err != nil {
return nil, nil, fmt.Errorf("error reading certificate from %s: %v", certificatePath, err)
}

// 4. MSI credentials
msiEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token"
if config.EnableMsi != nil {
enableMsi = *config.EnableMsi
var certs []*x509.Certificate
var key crypto.PrivateKey
if certificatePassword == "" {
certs, key, err = azidentity.ParseCertificates(loadFile, nil)
} else {
certs, key, err = azidentity.ParseCertificates(loadFile, []byte(certificatePassword))
}

if config.MsiEndpoint != nil {
msiEndpoint = *config.MsiEndpoint
if err != nil {
return nil, nil, fmt.Errorf("error parsing certificate from %s: %v", certificatePath, err)
}

cred, err = azidentity.NewClientCertificateCredential(
tenantID,
clientID,
certs,
key,
&azidentity.ClientCertificateCredentialOptions{
ClientOptions: policy.ClientOptions{
Cloud: cloudConfiguration,
},
},
)
if err != nil {
logger.Error("GetGraphClient", "client_certificate_credential_error", err)
return nil, nil, err
}
} else if enableMsi { // Managed identity authentication
cred, err = azidentity.NewManagedIdentityCredential(
&azidentity.ManagedIdentityCredentialOptions{},
)
if err != nil {
logger.Error("GetGraphClient", "managed_identity_credential_error", err)
return nil, nil, err
}
}

// 5. Default to CLI credentials
authMethod = "CLI"

if tenantID == "" {
authMethod = "CLI"
authConfig.EnableAzureCliToken = true
} else if tenantID != "" && clientID != "" && clientSecret != "" {
authConfig.TenantID = tenantID
authConfig.ClientID = clientID
authConfig.ClientSecret = clientSecret
authConfig.EnableClientSecretAuth = true
authMethod = "EnableClientSecretAuth"
} else if tenantID != "" && clientID != "" && certificatePath != "" && certificatePassword != "" {
authConfig.TenantID = tenantID
authConfig.ClientID = clientID
authConfig.ClientCertPath = certificatePath
authConfig.ClientCertPassword = certificatePassword
authConfig.EnableClientCertAuth = true
authMethod = "EnableClientCertificateAuth"
} else if enableMsi {
authConfig.EnableMsiAuth = true
authConfig.MsiEndpoint = msiEndpoint
authConfig.TenantID = tenantID
authConfig.ClientID = clientID
authMethod = "EnableMsiAuth"
}
return
auth, err := a.NewAzureIdentityAuthenticationProvider(cred)
if err != nil {
return nil, nil, fmt.Errorf("error creating authentication provider: %v", err)
}

adapter, err := msgraphsdkgo.NewGraphRequestAdapter(auth)
if err != nil {
return nil, nil, fmt.Errorf("error creating graph adapter: %v", err)
}
client := msgraphsdkgo.NewGraphServiceClient(adapter)

// Save session into cache
d.ConnectionManager.Cache.Set(sessionCacheKey, client)

return client, adapter, nil
}

// https://github.com/Azure/go-autorest/blob/3fb5326fea196cd5af02cf105ca246a0fba59021/autorest/azure/cli/token.go#L126
Expand Down
Loading