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

Check SDK versions via version check call #2365

Merged
merged 6 commits into from
Jan 21, 2022
Merged

Conversation

bergundy
Copy link
Member

What changed?

Version check call now includes SDK versions

Why?

To be able to alert when they use an outdated or vulnerable SDK version

How did you test it?

Added unit tests

Potential risks

Is hotfix candidate?

No

Closes #2333

@bergundy bergundy requested review from cretz, alexshtin, yiminc and a team January 12, 2022 01:40
@bergundy bergundy self-assigned this Jan 12, 2022
@@ -247,7 +257,7 @@ func (wh *WorkflowHandler) RegisterNamespace(ctx context.Context, request *workf
return nil, errShuttingDown
}

if err := wh.versionChecker.ClientSupported(ctx, wh.config.EnableClientVersionCheck()); err != nil {
if err := wh.RecordClientVersionAndCheckIfSupported(ctx); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not do this in interceptor?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just following the pattern that we already had with the version checker, I'll look into moving it into an interceptor

// Store if wasn't added racy
valIface, _ = vc.sdkInfoCounter.LoadOrStore(info, &sdkCount{})
}
atomic.AddInt64(&valIface.(*sdkCount).count, 1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the counter will essentially be request count make by this type of sdk?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have decided not to collect any stats.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it is reset before we send the request

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can change this to be a set instead of counting

@bergundy bergundy force-pushed the sdk-version-check branch 2 times, most recently from 8da885f to fde9b67 Compare January 12, 2022 08:43
@bergundy bergundy requested a review from yiminc January 12, 2022 09:12
common/rpc/interceptor/sdk_version.go Outdated Show resolved Hide resolved
common/rpc/interceptor/sdk_version.go Outdated Show resolved Hide resolved
common/rpc/interceptor/sdk_version.go Outdated Show resolved Hide resolved
common/rpc/interceptor/sdk_version.go Outdated Show resolved Hide resolved
}

func (vi *SDKVersionInterceptor) GetAndResetSDKInfo() []check.SDKInfo {
sdkInfo := make([]check.SDKInfo, 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cap init to the same size as vi.sdkInfoCounter

Copy link
Member

@cretz cretz Jan 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to suggest this, but since it's not thread safe I figured meh.

Technically if you set the cap as 1000 and a then len changed in another thread making it 1001 before you ranged, once you attempted append on the 1001st item, it'd create a new backing array of size 2002 ((cap + 1) * 2). Granted that's not as likely in an expected-new-key-to-be-rare map like this.

But negligible either way I figured.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's negligible but don't mind adding it

common/rpc/interceptor/sdk_version.go Outdated Show resolved Hide resolved
common/rpc/interceptor/sdk_version.go Outdated Show resolved Hide resolved
return &SDKVersionInterceptor{
sdkInfoSet: make(map[sdkNameVersion]bool),
lock: sync.RWMutex{},
infoSetSizeCapGetter: infoSetSizeCapGetter,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of this getter? Can't you just have a fixed size? If the size doesn't change at runtime, then just accept a normal int here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw that I only had dynamic config available so that's what I used.
The getter here is to avoid using the frontend Config struct directly because that causes an import cycle.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you just pass in the result of the getter during construction instead of storing the getter?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But then it wouldn't make sense to call this dynamic config

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure such a config needs to be dynamic (such an "extreme max" value really is better as just a const IMO so as not to add noise to the pieces that actually deserve to be configurable). But 🤷 it matters little.

common/rpc/interceptor/sdk_version.go Outdated Show resolved Hide resolved
common/rpc/interceptor/sdk_version.go Outdated Show resolved Hide resolved
common/rpc/interceptor/sdk_version.go Outdated Show resolved Hide resolved
common/rpc/interceptor/sdk_version.go Outdated Show resolved Hide resolved
common/rpc/interceptor/sdk_version.go Outdated Show resolved Hide resolved
Copy link
Member Author

@bergundy bergundy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll address the comments soon

common/rpc/interceptor/sdk_version.go Outdated Show resolved Hide resolved
return &SDKVersionInterceptor{
sdkInfoSet: make(map[sdkNameVersion]bool),
lock: sync.RWMutex{},
infoSetSizeCapGetter: infoSetSizeCapGetter,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw that I only had dynamic config available so that's what I used.
The getter here is to avoid using the frontend Config struct directly because that causes an import cycle.

common/rpc/interceptor/sdk_version.go Outdated Show resolved Hide resolved
common/rpc/interceptor/sdk_version.go Outdated Show resolved Hide resolved
common/rpc/interceptor/sdk_version.go Outdated Show resolved Hide resolved
common/rpc/interceptor/sdk_version.go Outdated Show resolved Hide resolved
common/rpc/interceptor/sdk_version.go Outdated Show resolved Hide resolved
@bergundy bergundy force-pushed the sdk-version-check branch 2 times, most recently from 31c6e67 to 429cda1 Compare January 14, 2022 06:54
}

type SDKVersionInterceptor struct {
sdkInfoSet map[sdkNameVersion]bool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, can use map[sdkNameVersion]struct{}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup brought up at #2365 (comment) but figured it was inconsequential

@bergundy bergundy enabled auto-merge (squash) January 21, 2022 21:29
@bergundy bergundy merged commit 65e4958 into master Jan 21, 2022
@bergundy bergundy deleted the sdk-version-check branch January 21, 2022 21:56

type SDKVersionInterceptor struct {
sdkInfoSet map[sdkNameVersion]struct{}
lock sync.RWMutex
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lock usually goes as embedded struct.

Suggested change
lock sync.RWMutex
sync.RWMutex

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh nice, I can do that.

Comment on lines +36 to +45
type sdkNameVersion struct {
name string
version string
}

type SDKVersionInterceptor struct {
sdkInfoSet map[sdkNameVersion]struct{}
lock sync.RWMutex
maxSetSize int
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is more than one type declaration they both should go inside single type decalration:

Suggested change
type sdkNameVersion struct {
name string
version string
}
type SDKVersionInterceptor struct {
sdkInfoSet map[sdkNameVersion]struct{}
lock sync.RWMutex
maxSetSize int
}
type (
sdkNameVersion struct {
name string
version string
}
SDKVersionInterceptor struct {
sdkInfoSet map[sdkNameVersion]struct{}
lock sync.RWMutex
maxSetSize int
}
)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, can we get a lint rule that enforces this style?


// RecordSDKInfo records name and version tuple in memory
func (vi *SDKVersionInterceptor) RecordSDKInfo(name, version string) {
info := sdkNameVersion{name, version}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would avoid nameless struct initialization. It is not possible to find references with it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it'd be okay in this simple case, I don't mind fixing

vi.lock.RLock()
overCap := len(vi.sdkInfoSet) >= vi.maxSetSize
_, found := vi.sdkInfoSet[info]
vi.lock.RUnlock()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlock call usually is called with deffer and it goes right after Lock. I understand the here it would require 2 more funcs and probably doesn't worth it though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's not worth it here


sdkInfo := make([]check.SDKInfo, 0, len(currSet))
for k := range currSet {
sdkInfo = append(sdkInfo, check.SDKInfo{Name: k.name, Version: k.version})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

k is the struct, right? You can use it directly, it will be copied by value:

Suggested change
sdkInfo = append(sdkInfo, check.SDKInfo{Name: k.name, Version: k.version})
sdkInfo = append(sdkInfo, k)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could have probably just used check.SDKInfo here, in the first iteration of this PR this struct had a times_seen field which we decided to drop.

Copy link
Member Author

@bergundy bergundy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @alexshtin


sdkInfo := make([]check.SDKInfo, 0, len(currSet))
for k := range currSet {
sdkInfo = append(sdkInfo, check.SDKInfo{Name: k.name, Version: k.version})
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could have probably just used check.SDKInfo here, in the first iteration of this PR this struct had a times_seen field which we decided to drop.

vi.lock.RLock()
overCap := len(vi.sdkInfoSet) >= vi.maxSetSize
_, found := vi.sdkInfoSet[info]
vi.lock.RUnlock()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's not worth it here


// RecordSDKInfo records name and version tuple in memory
func (vi *SDKVersionInterceptor) RecordSDKInfo(name, version string) {
info := sdkNameVersion{name, version}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it'd be okay in this simple case, I don't mind fixing

Comment on lines +36 to +45
type sdkNameVersion struct {
name string
version string
}

type SDKVersionInterceptor struct {
sdkInfoSet map[sdkNameVersion]struct{}
lock sync.RWMutex
maxSetSize int
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, can we get a lint rule that enforces this style?


type SDKVersionInterceptor struct {
sdkInfoSet map[sdkNameVersion]struct{}
lock sync.RWMutex
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh nice, I can do that.

bergundy added a commit that referenced this pull request Feb 1, 2022
bergundy added a commit that referenced this pull request Feb 2, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add SDK version checks via server version check call
4 participants